[BUG] Regression in 2.1.228: streaming to a Bedrock gateway returns zero events, client silently falls back to non-streaming, throughput halves
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?
Starting with 2.1.228, the streaming API request from Claude Code to an
Anthropic-compatible Bedrock gateway returns a stream that closes without ever
sending a message_start event. The client catches this and silently retries the
request in non-streaming mode. The retry succeeds, so the session works and
nothing surfaces as an error, but two things change:
- Wall-clock time roughly doubles. On identical ~530-token responses,
throughput goes from 53.4 tok/s to 25.9 tok/s.
- Nothing renders incrementally. The whole reply arrives at the end.
I bisected this across five releases. 2.1.226 and 2.1.227 stream normally.
2.1.228 is the first release that fails, and 2.1.229, 2.1.233 and 2.1.235 all
fail the same way.
| build | streams natively | tok/s (warm, ~530-token response) |
|---|---|---|
| 2.1.226 | yes (ttft_stream_ms 1640-1774) | 53.4 (n=10, range 47.2-56.9) |
| 2.1.227 | yes (ttft_stream_ms 1893-5476)
| 2.1.228 | no | 25.0 (n=2) |
| 2.1.229 | no | 26.4 (n=6) |
| 2.1.235 | no | 25.9 (n=10) |
"Streams natively" was tested with CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK=1,
which forbids the silent retry so the streamcall
fails outright.
Independent confirmation off the wire, using--output-format stream-json --include-partipt:content_block_delta` events with the first arriving at
2.1.226 emits 150-153
47% of the call. 2.1.228, 2.1.229 and 2.1.23s and
the response lands at 98% of the call.
The loss is not a fixed per-call cost. Holding output size fixed at four levels,
the ratio to 2.1.226 is flat: 0.51x at 4 out53x at
~2,000, 0.64x at ~6,000.
The severity here is mostly that it is silent. A visible warning would have saved
about a week of measurement on my side.
What Should Happen?
The streaming request should succeed on 2.1.228+ as it does on 2.1.227, and
responses should render incrementally at full throughput.
Failing that, the fallback should not be silent. When the client abandons
streaming and retries in non-streaming mode, it should surface a visible warning
once per session, since the user is now paying roughly 2x wall clock with no
indication anything changed. Right now the only trace is in --debug output.
Error Messages/Logs
# With CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK=1, the result JSON on 2.1.228+:
"result": "API Error: Stream ended without receiving any events"
"terminal_reason": "api_error"
"is_error": true
"ttft_stream_ms": null
# From --debug --debug-file on 2.1.228, 2.1.229 and 2.1.235 (default env,
# fallback allowed) -- this is the only trace the fallback leaves:
[ERROR] Stream completed without receiving message_start event - triggering non-streaming fallback
[ERROR] Error streaming, falling back to non-streaming mode: Stream ended without receiving any events
# With the fallback disabled, the same two lines plus:
[ERROR] Error streaming (non-streaming fallback disabled): Stream ended without receiving any events
[ERROR] Error in API request: Stream ended without receiving any events
# The equivalent line on 2.1.227, which works:
[DEBUG] Stream started - received first chunk
Steps to Reproduce
Requires a Bedrock deployment behind an Anthropic-compatible gateway. My env:
CLAUDE_CODE_USE_BEDROCK=1
ANTHROPIC_BEDROCK_BASE_URL=https://<corporate-gateway>/v1.0/model/bedrock
ANTHROPIC_MODEL=opus[1m]
- Download 2.1.227 and 2.1.235 as standalone binaries (I used the versioned
release bucket so I could test without installing over my working build).
- Forbid the silent retry, so a broken streaming path fails visibly instead of
being papered over:
export CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK=1
- Ask each build for one word:
./claude227.exe -p "Reply with exactly one word: yes." --output-format json
./claude235.exe -p "Reply with exactly one word: yes." --output-format json
2.1.227 returns "result": "yes" with ttft_stream_ms around 1900.
2.1.235 returns "is_error": true and
"result": "API Error: Stream ended without receiving any events".
- To see it without the env var, i.e. as it actually behaves day to day, count
the streaming deltas on a response long enough to stream:
unset CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK
./claude227.exe -p "Write exactly 300 words about confidence intervals." \
--output-format stream-json --verbose --include-partial-messages \
| grep -c content_block_delta
./claude235.exe -p "Write exactly 300 words about confidence intervals." \
--output-format stream-json --verbose --include-partial-messages \
| grep -c content_block_delta
2.1.227 gives ~150. 2.1.235 gives 0. Both return the full text; only 235
takes about twice as long and delivers it all at the end.
- Bisect if useful: 2.1.226 and 2.1.227 pass step 3, 2.1.228 onward fail.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.227
Claude Code Version
2.1.235 (Claude Code)
Platform
AWS Bedrock
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
Things I ruled out by measurement, to save triage time:
- Model. 2.1.228 fails identically on
opus[1m],claude-opus-5and
claude-sonnet-5. 2.1.227 streams on all three.
- Effort level. 2.1.226 reads 50.3 and 53.6 tok/s at
lowagainst 53.4 and
55.0 at max. 2.1.235 reads 27.5 at low against 25.9 and 27.4 at max.
- Extended thinking volume. Thinking-delta characters are 0 on every
streamed call on both working builds, so there is no hidden thinking block
accounting for the extra time.
- Request size. Prompt totals are 22,953 / 23,008 / 23,028 tokens on
226 / 229 / 235. 2.1.235 actually sends fewer tools, 22 against 26.
- Config toggles. Neither
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1nor
CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING=0 changes the outcome on
2.1.228 or 2.1.235.
- Prompt caching. Cached share of the prompt is ~100% on the failing builds,
so this is not a cache-miss effect.
- Throttling. Zero 429 and zero 529 across the whole window.
Possible lead, offered with low confidence: a static string scan found three
dated beta tokens present in the 2.1.228 bundle and absent from 2.1.227
(agent-memory-2026-07-22, dreaming-2026-04-21, mcp-tunnels-2026-06-22).
Presence in a 296 MB bundle is not evidence they are sent on the request, and
the betas toggle above did not change anything, so this may be nothing. Whatever
2.1.228 changed about the streaming request, my gateway answers 2.1.227's
version of it and not 2.1.228's. I cannot see the wire, so I cannot say which
side is at fault.
One note that may help others debugging streaming issues: ttft_ms in the
result JSON is not time to first token. On 2.1.226 it reads 10,384 ms on a
10.2s call and 185,898 ms on a 185.9s call, i.e. it tracks total duration even
when streaming is working normally. ttft_stream_ms appears to be the real
field, and it is null on the failing builds.
I have a repro harness (bisect with checksum-verified downloads, streaming
probe, throughput bench with a known-slow control arm) and can run any test
against 2.1.227 vs 2.1.228+ on this gateway if that helps narrow it.