Skip to content

fix(streaming): route Anthropic stream cleanup to the Anthropic client, not OpenAI rebuild (#28161) - #53926

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-f1139e66
Jun 28, 2026
Merged

fix(streaming): route Anthropic stream cleanup to the Anthropic client, not OpenAI rebuild (#28161)#53926
teknium1 merged 2 commits into
mainfrom
hermes/hermes-f1139e66

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Stream-cleanup on Anthropic-native sessions no longer rebuilds the OpenAI primary client — eliminating a ~15-minute hang on stuck streams.

Root cause: three stream-cleanup paths in agent/chat_completion_helpers.py called _replace_primary_openai_client() unconditionally. On api_mode == "anthropic_messages" (Anthropic-only configs, no OPENAI_API_KEY) that rebuild fails with a missing-credentials error AND the wedged in-flight httpx stream is never closed, so the worker thread blocks on the dead socket until the 900s httpx read-timeout fires. The _interrupt_requested branch in the same function already did the right thing for Anthropic (_anthropic_client.close() + _rebuild_anthropic_client()); the three cleanup sites now mirror it.

Changes

  • agent/chat_completion_helpers.py: branch each of the three pool-cleanup sites on api_mode — Anthropic mode closes + rebuilds the Anthropic client; everything else rebuilds the OpenAI primary client as before:
    • stream_mid_tool_retry_pool_cleanup
    • stream_retry_pool_cleanup
    • stale_stream_pool_cleanup
  • tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py: new tests for the two reachable Anthropic cleanup sites (stream-retry, stale-stream) asserting the Anthropic close+rebuild fires and the OpenAI rebuild does not.
  • tests/run_agent/test_streaming.py: re-point test_anthropic_stream_parser_valueerror_retries_before_delivery — it previously asserted _replace_primary_openai_client was called once (passing because the bug was live). It now asserts the corrected close+rebuild-Anthropic behavior.

Validation

Before After
Anthropic stale/retry stream cleanup rebuilds OpenAI client → fails, stream left open, ~15 min hang closes + rebuilds Anthropic client, stream torn down immediately
_replace_primary_openai_client calls on Anthropic path 1 0
Streaming test suite green while bug live 40/40 pass, assertions reflect fix

E2E (real imports, OPENAI_API_KEY unset, api_mode=anthropic_messages): a transient stream error triggers cleanup → _replace_primary_openai_client called 0 times, _rebuild_anthropic_client + _anthropic_client.close() each called once, retry succeeds.

Salvaged from #28240 by @EloquentBrush0x (earliest dedicated fix for this issue); conflict resolved against current main's newer request-client cleanup helper, sibling tests + the bug-encoding assertion fixed on top.

Infographic

stream-cleanup-routing

EloquentBrush0x and others added 2 commits June 27, 2026 19:24
… OpenAI client

interruptible_streaming_api_call() has three connection-pool cleanup
sites that called _replace_primary_openai_client() unconditionally.
For api_mode=anthropic_messages this has two consequences:

1. _replace_primary_openai_client() fails (OPENAI_API_KEY unset on
   Anthropic-only configs), so dead connections are never purged.
2. The stale-stream detector's outer-poll site (L1977) is the only
   mechanism that can interrupt the worker thread while it blocks in
   for event in stream:. Because the Anthropic client is never closed,
   the thread stays blocked until the 900 s httpx read-timeout fires,
   producing a visible 15-minute hang for Telegram/gateway users on
   claude-opus-4-7.

Fix: mirror the existing interrupt-path pattern (L1989-1997) at all
three cleanup sites — if api_mode == "anthropic_messages", call
_anthropic_client.close() + _rebuild_anthropic_client() instead of
_replace_primary_openai_client(). _rebuild_anthropic_client() handles
both direct Anthropic and Bedrock-hosted Claude correctly, unlike the
inline build_anthropic_client() calls in open PR #14430.

PR #14430 (open) covers only the outer stale-detector site (L1977).
PR #23678 (open) covers only the inner retry sites (L1774, L1833).
This PR covers all three sites and uses _rebuild_anthropic_client()
for Bedrock parity.

Fixes #28161
…ld path

The existing test_anthropic_stream_parser_valueerror_retries_before_delivery
asserted mock_replace.call_count == 1 — i.e. it passed precisely because the
buggy OpenAI rebuild was invoked on the Anthropic path. Repoint it to assert
the corrected close+rebuild-Anthropic behavior (#28161).
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-f1139e66 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11581 on HEAD, 11524 on base (🆕 +57)

🆕 New issues (14):

Rule Count
invalid-argument-type 11
unresolved-import 2
unresolved-attribute 1
First entries
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `list[dict[str, Any]]`, found `str | bool`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `list[str] | None`, found `str | bool`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `int | float | None`, found `str | bool`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:19: [unresolved-import] unresolved-import: Cannot resolve imported module `httpx`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `list[str]`, found `str | bool`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:20: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `str`, found `str | bool`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `dict[str, Any]`, found `str | bool`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `bool`, found `str | bool`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `IterationBudget`, found `str | bool`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:41: [unresolved-attribute] unresolved-attribute: Unresolved attribute `api_mode` on type `AIAgent`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `int`, found `str | bool`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `((str, dict[Unknown, Unknown], /) -> None) | None`, found `str | bool`
tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py:40: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `int | float`, found `str | bool`

✅ Fixed issues: none

Unchanged: 6069 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/anthropic Anthropic native Messages API P2 Medium — degraded but workaround exists labels Jun 28, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Saturated competing cluster for #28161 (all open): #28240, #33855, #44076, #47797 — all fix the unconditional _replace_primary_openai_client() rebuild on api_mode==anthropic_messages stream-cleanup paths (the ~15-min hang). This maintainer PR is the authoritative three-site version. Flagging for a human to pick one and close the rest; not marking any as duplicate.

@teknium1
teknium1 merged commit 6dcc579 into main Jun 28, 2026
30 checks passed
@teknium1
teknium1 deleted the hermes/hermes-f1139e66 branch June 28, 2026 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/anthropic Anthropic native Messages API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants