Skip to content

fix(python-client): fail loud when a stream opens on a redirect - #4173

Open
ghoshp83 wants to merge 1 commit into
omnigent-ai:mainfrom
ghoshp83:fix/stream-open-redirect-silent-empty
Open

fix(python-client): fail loud when a stream opens on a redirect#4173
ghoshp83 wants to merge 1 commit into
omnigent-ai:mainfrom
ghoshp83:fix/stream-open-redirect-silent-empty

Conversation

@ghoshp83

@ghoshp83 ghoshp83 commented Aug 5, 2026

Copy link
Copy Markdown

Closes #4226

What

When opening a stream, the client silently swallows a redirect. Its httpx.AsyncClient is built with httpx's default follow_redirects=False, and the stream-open guard only rejected status >= 400:

if resp.status_code >= 400:
    await resp.aread()
    raise_for_status(resp.status_code, response_body(resp))

So a proxy or gateway answering a 3xx (e.g. 302) on the stream request is neither followed nor rejected — it falls through to the SSE parser, which finds no data: frames in the redirect body and yields nothing. The caller gets a silent, error-free, zero-event stream and cannot tell it apart from "the session produced nothing".

_stream_session_events already documents :raises OmnigentError: If the server returns a non-2xx status, so this is the redirect half of a contract the rest of the sessions namespace already keeps.

Fix

Reject a 3xx on the stream open at both sites — SessionsNamespace.stream (via _stream_session_events) and the deprecated /v1/responses stream — raising OmnigentError instead of decoding an empty stream. raise_for_status is a no-op below 400, so the redirect case needs its own arm. Redirects stay disabled rather than followed, keeping the client's credential-safety posture intact.

Test

tests/frontends/sdk/test_stream_redirect_guard.py drives _stream_session_events with an httpx.MockTransport that returns a 302, and asserts the open raises OmnigentError (status 302) and yields zero events. It fails against the old >= 400 guard (which completes empty, no error) and passes with the fix.

Not in this PR

While here, _CODE_MAP in _errors.py is built inside raise_for_status but never read (classification is by status code) — it looks like dead code. Left out to keep this change focused; happy to send a separate cleanup.

Credit

Spotted by @bdchatham while comparing this client against the Go client during review of #4010.

@github-actions github-actions Bot added the size/M Pull request size: M label Aug 5, 2026
@github-actions
github-actions Bot requested a review from dbczumar August 5, 2026 21:45
The SDK's httpx.AsyncClient uses httpx's default follow_redirects=False, and the
stream-open guard only rejected status >= 400. A proxy or gateway answering a
3xx (e.g. 302) on the stream request therefore fell through to the SSE parser,
which found no data: frames in the redirect body and yielded nothing — handing
the caller a silent, error-free, zero-event stream instead of surfacing the
connection that never reached the server. _stream_session_events already
documents ":raises OmnigentError: If the server returns a non-2xx status", so
this is the redirect half of a contract the rest of the sessions namespace keeps.

Reject a 3xx on the stream open at both sites — SessionsNamespace.stream (via
_stream_session_events) and the deprecated /v1/responses stream — raising
OmnigentError rather than decoding an empty stream. Adds a regression test that
returns a 302 from an httpx.MockTransport and asserts the open raises instead of
completing with zero events; it fails against the old >= 400 guard.

Spotted by @bdchatham while comparing this client against the Go client during
review of omnigent-ai#4010.

Signed-off-by: ghoshp83 <pralay.ghosh@gmail.com>
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@ghoshp83 Thanks for the PR! It doesn't reference an issue yet.

We require an issue for every PR, so the work can be prioritized before it's reviewed. Add one to the description:

  • Closes #123 if this PR finishes the issue. That links it, gives your PR the issue's priority, and closes the issue when this merges. You can also link it from the Development section of the sidebar.
  • Part of #123 if this is one step towards it. Related to, Towards, and Refs work the same way, and leave the issue open.

No issue exists for this yet? Open one first, then reference it. That's how we track what's worth doing, and it's usually quicker than it sounds. Note a reference has to point at an issue: naming another PR doesn't count.

The only exceptions are changes with no user-visible behaviour: pure Refactor / chore, Docs, or Test / CI work. If that's genuinely what this is, check that box under Type of change. Anything that fixes a bug, adds a feature, or changes the UI needs an issue, even when it also touches docs or tests.

See CONTRIBUTING.md for the full policy.

No action is taken beyond this comment.

@github-actions github-actions Bot added P2-medium Priority: bug with workaround, important feature request waiting-for-review labels Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2-medium Priority: bug with workaround, important feature request size/M Pull request size: M waiting-for-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

python-client: stream open silently swallows a redirect (zero-event, error-free stream)

2 participants