Skip to content

fix(decoders): close the response on a jsonl decode or transport error - #1932

Open
adhavan18 wants to merge 1 commit into
anthropics:mainfrom
adhavan18:fix-jsonl-decoder-leak-on-error
Open

adhavan18 wants to merge 1 commit into
anthropics:mainfrom
adhavan18:fix-jsonl-decoder-leak-on-error

Conversation

@adhavan18

Copy link
Copy Markdown

Description

When a batch-result JSONL stream raises a JSON decoding error, its HTTP response stays open. JSONLDecoder.__decode__ / AsyncJSONLDecoder.__decode__ only close http_response when the generator runs to completion normally — an exception raised mid-decode (from json.loads, or a transport error from the underlying byte stream) propagates straight out and skips the close entirely.

Fixed by wrapping both generator bodies in try/finally calling close() / await close(), matching the existing cleanup pattern already used by the SSE stream decoder (Stream.__stream__ / AsyncStream.__stream__).

Closes #1928

Verification

Added test_sync_closes_response_on_decode_error and test_async_closes_response_on_decode_error to tests/decoders/test_jsonl.py. These construct a real httpx2.Response backed by an actual ByteStream (not a bare Response(200), which starts is_closed=True with nothing to close and would make the test a no-op regardless of the fix — caught this while writing the test), feed invalid JSON through the decoder, and assert response.is_closed afterward. Both confirmed to fail on unfixed main and pass with the fix. Full tests/decoders/test_jsonl.py suite: 8 passed.

I initially also wrote tests for a raised transport error (httpx2.ReadError from the byte stream itself), but those passed regardless of the fix — httpx2's own iter_bytes()/aiter_bytes() already closes the response internally when the underlying stream raises, so that specific path isn't a real gap. Kept only the two tests that actually distinguish fixed from unfixed behavior.

__decode__ and AsyncJSONLDecoder.__decode__ only closed the underlying
http_response when the generator ran to completion. a json decode error,
or a transport error from the underlying byte stream, propagated straight
out of the generator and left the connection open, since close() was never
called on any non-normal exit path.

wrapped both generator bodies in try/finally calling close()/await close(),
matching how the SSE stream decoder already handles this.

verified with two new tests that construct a real httpx2 stream (not a bare
Response(200), which starts pre-closed and would make the test a no-op) and
confirm response.is_closed is True after a decode error - both fail on
unfixed main and pass with the fix.
@adhavan18
adhavan18 requested a review from a team as a code owner September 14, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Batch result decoders leave responses open after parsing or transport errors

1 participant