SSE heartbeat (≤30s) during model reasoning to prevent 'Connection closed mid-response' / stream-watchdog stalls on long (esp. Agent-tool) turns

Status Open
Maintainer reply None cached
Activity 6 comments · opened Jun 22, 2026

Summary

Long-running turns — especially Agent-tool subagent dispatches — frequently fail with:

  • API Error: Connection closed mid-response. The response above may be incomplete.
  • Agent stalled: no progress for <N>s (stream watchdog did not recover)

These happen during long silent model-reasoning spans, when the SSE response stream emits no bytes for an extended period. An intermediary (NAT/proxy/VPN) drops the idle connection, or the server closes it, and there is no client-side recovery for background/Agent-tool dispatches.

Request

Emit a periodic SSE heartbeat / comment line (≤30s) during model reasoning so the byte stream never goes idle long enough to be evicted or closed. This is the only true prevention — client-side keepalive (TCP keepidle) and watchdog-timeout tuning only mitigate or detect; they cannot stop an idle stream from being dropped mid-reasoning.

Why existing mechanisms don't cover this

  • The TCP keepalive path (e.g. lowering macOS net.inet.tcp.keepidle) only addresses NAT idle-eviction, not server-side closes or H2-layer silence.
  • CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS is a kill threshold, not a recovery.
  • The stream-retry behavior (tengu_streaming_stale_connection_retry) appears to apply only to the foreground streaming path — Agent-tool subagent dispatches get no retry path regardless of flag state. Multi-agent / orchestration workloads (where one parent fans out many long-running subagents) are therefore the most exposed and the least covered.

Impact

In an autonomous multi-agent orchestrator, a single mid-reasoning close kills a subagent that may have run for many minutes. Mitigations (frequent commits, micro-dispatching, auto-resume) reduce data loss but cannot prevent the close. A server-side heartbeat would eliminate the failure class for all consumers, foreground and background alike.

Alternative / complementary ask

If a heartbeat isn't feasible, expose a client-settable retry/resume for background (Agent-tool) dispatches — equivalent to the foreground stale-connection retry — so orchestrators can recover transparently.

Environment

  • Observed on macOS (darwin), Claude Code CLI, both foreground turns and Agent-tool subagent dispatches.
  • Reproduces most reliably on long agent turns with extended reasoning between tool calls.

View original on GitHub ↗

5 Comments

github-actions[bot] · 2 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/45224
  2. https://github.com/anthropics/claude-code/issues/37534
  3. https://github.com/anthropics/claude-code/issues/47059

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

WouterDeBot · 2 months ago

Bumping this to prevent auto-close — the underlying gap remains unaddressed.

To clarify why this is distinct from the cited issues: the specific ask is a server-sent SSE heartbeat at ≤30s cadence specifically during extended model reasoning, not only between tokens or during tool execution. Long reasoning windows are the exact interval where the stream goes idle and the TCP connection is silently dropped by intermediaries.

The critical compounding factor: Agent-tool (subagent) dispatches receive no retry. They are foreground-only — a mid-reasoning connection drop kills the dispatch permanently with no recovery path. A reasoning-phase heartbeat is the only server-side fix that closes this failure class; client-side keepalive tuning is insufficient.

For reference, the three issues cited as "duplicates" when this was closed are all closed without resolution: #45224 (closed NOT_PLANNED), #37534 (closed DUPLICATE), and #47059 (closed NOT_PLANNED). The underlying problem therefore remains open.

This continues to cause real data loss in long agentic workflows. Appreciate any update on whether a reasoning-phase heartbeat is on the roadmap.

WouterDeBot · 1 month ago

Still hitting this regularly on Claude Code with long-running agentic sessions (Opus 4.8, heavy multi-subagent orchestration). In one ~heavy session today we recorded 7 distinct "Connection closed mid-response" main-loop deaths, including one silent window of ~1h42m while a background subagent ran. The pattern is consistent with the client runtime not setting SO_KEEPALIVE on the SSE connection: during a long silent reasoning/tool-wait window (no bytes flowing), a NAT/CDN hop evicts the idle connection at ~60–350s and the stream is dropped.

Client-side mitigations (turn-death markers + auto-resume from persisted state) recover the work, but they don't prevent the interruption — the user still sees the error and has to nudge a resume. A server-sent SSE heartbeat/keepalive frame (≤30s cadence) during long reasoning/tool execution is the only thing that actually prevents the eviction. Would love to see this prioritized — it's the single highest-leverage fix for long-horizon agent reliability. (Related: #45224, #37534, #47059.)

asawicki · 1 month ago

Corroborating data point (Claude Code 2.1.202, macOS) — confirms this hits the Agent-tool path exactly as described.

In a single ~4.5 h multi-agent orchestration run (a parent fanning out parallel Task subagents), I counted 11 genuine API Error: Connection closed mid-response events, ground-truthed via isApiErrorMessage: true in the JSONL transcripts to exclude quoted/echoed mentions. They were spread across the run (09:13–13:33), not a single burst — so not one upstream incident, but a recurring failure on long turns.

Two forensic details that back the "no client-side recovery for Agent-tool dispatches" point:

  1. Each affected subagent transcript ends exactly at the error record — zero records after it. The subagent dies mid-turn; nothing inside it retries or resumes. Recovery only happened because the parent orchestrator detected the dead worker and re-dispatched — precisely the "mitigations reduce data loss but cannot prevent the close" situation described here.
  2. A naive re-dispatch that re-runs the same large turn drops again. In one case the first retry hit Connection closed mid-response on the same big turn; only a subsequent resume from a checkpoint (a smaller remaining turn) got through. So orchestrator-level auto-resume is only a partial mitigation — it costs extra full dispatches and still repeats the drop unless the retry is made smaller.

Trigger correlated with a large single-message emit (a ~9 KB file Write) following a long, investigation-heavy (silent-reasoning) span under accumulated context — consistent with the "long silent reasoning span → idle SSE stream → dropped mid-response" mechanism in the summary.

+1 for the server-side SSE heartbeat. The complementary ask — a client-settable retry/resume for background (Agent-tool) dispatches, equivalent to the foreground stale-connection retry — would fully cover the multi-agent case even where a heartbeat isn't available, and would remove the "re-dispatch drops again" waste above.

WouterDeBot · 1 month ago

Keep-alive: still reproducing regularly as of late July. Long reasoning stretches with no SSE heartbeat continue to cause client-side connection closes on otherwise-healthy agents (multiple per session in our autonomous-orchestration workload; two more today — both agents survived server-side and completed after resume, confirming the drops are transport-level, not model-level). A ≤30s heartbeat during reasoning remains the only prevention that would work client-side. Happy to provide fresh traces if useful.

Showing cached comments. Read the full discussion on GitHub ↗