[Bug] Streaming through local plaintext HTTP proxies broken on Windows native builds 2.1.221-2.1.223 (chunked responses aborted/rejected by embedded Bun runtime)

Status Open
Reported on v2.1.221
Maintainer reply None cached
Activity 0 comments · opened Aug 6, 2026

Summary

On Windows native builds (2.1.221, 2.1.222, 2.1.223), Claude Code cannot consume streaming ("stream": true) responses when ANTHROPIC_BASE_URL points to a local plaintext HTTP/1.1 proxy that returns Transfer-Encoding: chunked. The client aborts the connection 1–3 s after the first response bytes (proxy observes WSAECONNABORTED 10053 / WSAECONNRESET 10054), or rejects the response even after the proxy has delivered the complete, well-framed chunked body (message_stop + terminating 0\r\n\r\n).

After ~10 failed streaming retries the client silently falls back to a non-streaming request, which succeeds — so claude -p runs look like they "work" but only after minutes of retry storm, while interactive sessions surface API Error: Connection closed mid-response. The response above may be incomplete.

Environment

  • Claude Code native win32-x64: 2.1.221, 2.1.222, 2.1.223 — all affected. Embedded runtime reports X-Stainless-Runtime: node, X-Stainless-Runtime-Version: v26.3.0 (Bun 1.4.0 based native binary).
  • Windows 11.
  • Local proxy: cc-switch (http://127.0.0.1:15721) → Anthropic-compatible upstream (MiniMax /anthropic endpoint). Also reproduced with a minimal ~20-line Node.js HTTP passthrough proxy, so this is not proxy-vendor-specific.
  • A previous install (before upgrading to 2.1.222 on 2026-08-05) streamed through the same proxy without issues.

Controlled experiments — what works vs. what fails

| Client | Path | Result |
|---|---|---|
| claude.exe 2.1.221–2.1.223 | → local plaintext HTTP proxy (chunked SSE) | FAIL — every stream aborted/rejected |
| claude.exe 2.1.223 | → HTTPS origin directly (chunked SSE over TLS) | OK |
| claude.exe 2.1.223 | → same proxy, non-streaming request (Content-Length response) | OK |
| curl / Node 22 fetch / Bun 1.3.12 fetch | → same proxy, same streaming request | OK (10/10) |
| claude.exe 2.1.223 | → local Node bridge that strips chunked framing (EOF-terminated body, Connection: close) | OK — first-try success every time |

The single variable that flips pass/fail in the A/B: whether the local server's response uses chunked framing. Same server software (Node http), same upstream, same request body — chunked passthrough fails, EOF-terminated succeeds.

Evidence

  • Proxy-side byte capture of failing streams shows well-formed chunked SSE (2B5\r\n\r\n0\r\n\r\n) and ordinary response headers (content-type: text/event-stream; charset=utf-8, transfer-encoding: chunked, no Content-Length conflict, no Connection weirdness).
  • Captured streams that were delivered in full (including message_stop and the terminal zero chunk) were still followed by client retries — i.e. the client rejected even complete responses.
  • Abort timing is consistently 1–3 s after the first response chunk; non-chunked responses over the same connection path are always accepted.

Hypothesis

The embedded Bun runtime (1.4.0) mishandles plaintext HTTP/1.1 chunked responses — aborting mid-stream and/or failing to accept end-of-body — while the TLS code path and the Content-Length / close-delimited paths work correctly. Bun 1.3.12's fetch handles the same stream fine, so this looks like a regression in the embedded runtime rather than in CLI-level logic.

Minimal repro

  1. Run a local HTTP proxy that forwards POST /v1/messages to any Anthropic-compatible streaming endpoint and preserves Transfer-Encoding: chunked (a ~20-line Node http passthrough is enough).
  2. ANTHROPIC_BASE_URL=http://127.0.0.1:<port> claude -p "say hi in one word"
  3. Observe a multi-minute retry storm; the proxy logs show the client aborting each streamed response 1–3 s in, then a final non-streaming fallback request succeeding.

Workaround (for other affected users)

Put a tiny local bridge between Claude Code and the proxy that de-chunks responses: strip the transfer-encoding header and deliver the body EOF-terminated (Connection: close, no Content-Length). Streaming then works reliably on first attempt while keeping the proxy (routing/usage stats/failover) in the path.

View original on GitHub ↗