[BUG] Stream reset (ECONNRESET) 2-8ms after first chunk on every /v1/messages streaming request, while non-streaming and third-party SSE are healthy (Windows, v2.1.241)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Every streaming request Claude Code makes to /v1/messages receives its first response chunk normally and is then reset by ECONNRESET a handful of milliseconds later. The client retries, and each retry fails the same way, until one attempt happens to survive. A single trivial prompt routinely takes 100-300 seconds and 5-13 resets, or exhausts the retry ladder entirely.
The signature is extremely consistent and is visible in --debug output:
[DEBUG] [API REQUEST] /v1/messages x-client-request-id=<id> source=sdk
[DEBUG] Stream started - received first chunk
[DEBUG] [API:timing] first byte after 1959ms
[WARN] Stream connection error (ECONNRESET) - retrying streaming (10/15)
Measured delay between Stream started - received first chunk and the ECONNRESET, across 24 resets in the debug logs:
0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 6, 6, 8, 19, 67, 571,
1254, 2761, 4920, 6049 (milliseconds)
Median is about 3 ms. Round-trip latency on this link is ~190 ms, so a 3 ms delta means the reset arrives essentially in the same flight as the first response data. This is far too fast to be any timeout, client-side or otherwise, which I confirmed experimentally (see below).
Time-to-first-byte is healthy throughout: typically 1.2-2.5 s, occasionally 6 s. So the server accepts the request, begins generating, emits a first chunk, and the connection dies immediately after.
What makes this reportable rather than a local network problem: every control I can devise passes, including controls that exercise exactly the same transport characteristics. Full measurements below.
What Should Happen?
A short prompt returns in a few seconds without connection errors, as it does for non-streaming requests to the same endpoint from the same machine in the same minute.
Error Messages/Logs
Debug log excerpt (one representative session)
2026-08-24T13:49:08.885Z [DEBUG] Failed to fetch Grove notice config: essential-traffic-only
2026-08-24T13:49:10.599Z [DEBUG] Stream started - received first chunk
2026-08-24T13:49:16.648Z [WARN] Stream connection error (ECONNRESET) - retrying streaming (1/15)
2026-08-24T13:49:19.070Z [DEBUG] Stream started - received first chunk
2026-08-24T13:49:19.072Z [WARN] Stream connection error (ECONNRESET) - retrying streaming (2/15)
2026-08-24T13:49:22.050Z [DEBUG] Stream started - received first chunk
2026-08-24T13:49:22.051Z [WARN] Stream connection error (ECONNRESET) - retrying streaming (3/15)
2026-08-24T13:49:24.529Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=<id> source=sdk
2026-08-24T13:49:27.065Z [DEBUG] Stream started - received first chunk
2026-08-24T13:49:28.319Z [WARN] Stream connection error (ECONNRESET) - retrying streaming (4/15)
...
2026-08-24T13:53:20.732Z [DEBUG] Stream started - received first chunk
2026-08-24T13:53:20.732Z [DEBUG] [API:timing] first byte after 1850ms
Two other reset variants appear when the stream dies after emitting content:
[WARN] Stream connection closed (ECONNRESET) after 1 block(s) yielded - finalizing partial response
[WARN] Stream connection closed (ECONNRESET) after thinking-only yield - retrying streaming (1/2)
The first of these is what surfaces to the user as API Error: Connection lost mid-response. The response above may be incomplete.
User-visible errors
API Error: Connection lost mid-response. The response above may be incomplete.
Connection dropped (ECONNRESET) - Retrying in 14s - attempt 6/15
Plus indefinite hangs with no message at all in -p mode (>15 minutes observed).
Observability gap: api_retry reports no cause
The stream-json output reports these as unknown errors even though the debug log identifies ECONNRESET precisely:
json
{"type":"system","subtype":"api_retry","attempt":10,"max_retries":10,
"retry_delay_ms":38918,"error_status":null,"error":"unknown"}
error_status: null and error: "unknown" make this failure mode very hard to diagnose from stream-json alone. Surfacing the socket error code here would help a lot.
Response-time distribution is bimodal, and it's an artifact of the retry ladder
Six identical prompts: 12.7s, 17.7s, 391.5s, 408.1s, 411.5s, 418.2s. No intermediate values. The fast group is "no resets"; the slow group is the exponential backoff ladder, whose delays plateau around 33-39 s per attempt.
Failed attempts still consume server-side prompt cache
cache_read_input_tokens in the final result divides exactly by the number of attempts:
Attempts cache_read_input_tokens Per attempt
10 239,100 23,910
9 215,190 23,910
8 191,280 23,910
2 35,920 17,960
10 179,600 17,960
So each failed attempt reaches the API, is fully processed far enough to read the cached prompt, and is accounted for. One "explain CSS Grid in 200 words" prompt consumed 239,100 cache-read tokens. On a subscription plan this burns the 5-hour usage window; on metered billing it would be billed. This seems worth flagging independently of the root cause.
Steps to Reproduce
<html>
<body>
<!--StartFragment--><ol dir="ltr"><li><code>claude -p "explain CSS Grid in 200 words" --debug --debug-file out.txt --output-format stream-json --verbose</code></li>
<li>Inspect <code>out.txt</code> for <code>Stream connection error (ECONNRESET)</code></li>
<li>Compare the timestamp of each <code>Stream started - received first chunk</code> with
the ECONNRESET line immediately following it</li>
</ol>
<p dir="ltr">Reproduces in a clean directory with no CLAUDE.md and no MCP servers.
<code>~/.claude/settings.json</code> contains only <code>{"theme": "dark"}</code>.</p>
<hr>
<h2 dir="ltr">Environment</h2>
<div dir="ltr">
|
-- | --
Claude Code | 2.1.241, native install (also reproduced on 2.1.231)
OS | Windows 11 (build 10.0.26200)
Node | v24.19.0
Shell | PowerShell 5.1
Plan | Claude Pro, OAuth via "Claude.ai Subscription"
Models affected | Sonnet 5, Opus 5, Haiku 4.5 - all identical
Network | Commercial VPN (Astrill StealthVPN, UDP transport) from mainland China
IPv6 | none active
Proxy env vars | none set
TLS inspection | none - cert issuer is CN=WE1, O=Google Trust Services
Antivirus | Windows Defender only
</div>
<p dir="ltr">Requesting 20 yields 15. Possibly intentional, but the effective ceiling isn't
obvious from the outside.</p>
<h3 dir="ltr">What I'd find most useful</h3>
<ol dir="ltr">
<li>Confirmation of whether the reset originates at the API edge or is being
synthesised client-side by the SDK's stream handling.</li>
<li><code>error_status</code> / <code>error</code> in <code>api_retry</code> events populated with the actual
socket error, so <code>stream-json</code> consumers can see ECONNRESET rather than
<code>"unknown"</code>.</li>
<li>Whether failed attempts that consume prompt-cache reads are expected to count
against usage.</li>
</ol>
<p dir="ltr">Full debug logs, raw measurement transcripts and the test scripts are available
on request.</p><!--EndFragment-->
</body>
</html>
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
Unknown
Claude Code Version
v2.1.241
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
<html>
<body>
<!--StartFragment--><h2 dir="ltr">Additional Information</h2>
<h3 dir="ltr">CLAUDE_CODE_MAX_RETRIES appears to be capped at 15</h3>
<div dir="ltr">
Setting | Reported max_retries
-- | --
unset | 10
CLAUDE_CODE_MAX_RETRIES=20 | 15
</div>
<p dir="ltr">Requesting 20 yields 15. Possibly intentional, but the effective ceiling isn't
obvious from the outside.</p>
<h3 dir="ltr">What I'd find most useful</h3>
<ol dir="ltr">
<li>Confirmation of whether the reset originates at the API edge or is being
synthesised client-side by the SDK's stream handling.</li>
<li><code>error_status</code> / <code>error</code> in <code>api_retry</code> events populated with the actual
socket error, so <code>stream-json</code> consumers can see ECONNRESET rather than
<code>"unknown"</code>.</li>
<li>Whether failed attempts that consume prompt-cache reads are expected to count
against usage.</li>
</ol>
<p dir="ltr">Full debug logs, raw measurement transcripts and the test scripts are available
on request.</p><!--EndFragment-->
</body>
</html>