Skip to content

fix(decisioning): project malformed-JSON 2xx responses to AdcpError#767

Draft
bokelley wants to merge 2 commits into
mainfrom
claude/issue-453-json-decode-error-handling
Draft

fix(decisioning): project malformed-JSON 2xx responses to AdcpError#767
bokelley wants to merge 2 commits into
mainfrom
claude/issue-453-json-decode-error-handling

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Closes #453

The create_upstream_http_client and create_translation_map helpers were already fully implemented and exported from adcp.decisioning. This PR closes the one remaining acceptance-criteria gap: JSON decode errors.

When an upstream returns a successful (2xx) status with a non-JSON body (e.g. a CDN or proxy returns an HTML error page), UpstreamHttpClient._request previously propagated a raw json.JSONDecodeError from response.json(). Adopters catch AdcpError throughout; an unhandled ValueError subclass breaks that contract and is invisible in typed code. This PR wraps the call in except ValueError and re-raises as AdcpError(SERVICE_UNAVAILABLE, recovery="transient") — the same code used for 5xx responses, since an upstream returning HTML for a JSON endpoint is indistinguishable from a transient infrastructure failure.

What changed

  • src/adcp/decisioning/upstream.py: wrap response.json() in try/except ValueError; raise AdcpError(SERVICE_UNAVAILABLE) with recovery="transient" and from exc chain.
  • tests/test_upstream_helpers.py: add test_200_with_malformed_json_raises_service_unavailable — mocks a 200 with an HTML body and asserts code, recovery, and __cause__.

What tested

  • uv run pytest tests/test_upstream_helpers.py tests/test_upstream_for.py -q57 passed, 0 failed
  • uv run mypy src/adcp/decisioning/upstream.py — clean
  • uv run ruff check src/adcp/decisioning/upstream.py tests/test_upstream_helpers.py — clean

Pre-PR review

  • code-reviewer: approved — one nit fixed (except Exceptionexcept ValueError; __cause__ assertion added to test)
  • dx-expert: approved — SERVICE_UNAVAILABLE with recovery="transient" is correct for a CDN/proxy interception; nit on exception breadth addressed

Nits (not fixed):

  • await client.aclose() in the new test is not in a finally block; consistent with the rest of the file's existing test style — test-only resource leak only
  • Error message exposes raw exc text rather than a body-length hint; useful for debugging, acceptable

Triage-managed PR. This bot does not currently iterate on
review comments or PR conversation threads (only on the source
issue). To unblock:

  • Push fixup commits directly: gh pr checkout <num>
    fix → push.
  • Or re-trigger: comment /triage execute on the source
    issue.

See adcp#3121
for context.

Session: https://claude.ai/code/session_0172h7dGLvfEeo1DLKzAH4TZ


Generated by Claude Code

claude added 2 commits May 21, 2026 12:13
Catches JSONDecodeError from upstream.py's _request method and raises a
typed AdcpError(SERVICE_UNAVAILABLE, recovery="transient") so adopters
get a structured error rather than a raw decode exception when a proxy or
CDN returns an HTML error page with a 200 status.

Closes #453

https://claude.ai/code/session_0172h7dGLvfEeo1DLKzAH4TZ
Tightens the catch in UpstreamHttpClient._request from bare Exception to
ValueError (which covers json.JSONDecodeError and any custom decoder that
signals parse failure the same way), matching the narrower defensive
pattern used elsewhere in the file. Adds __cause__ assertion to the test.

https://claude.ai/code/session_0172h7dGLvfEeo1DLKzAH4TZ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(server): createUpstreamHttpClient + createTranslationMap (translator pattern helpers)

2 participants