Skip to content

fix: keep long extended-thinking streams intact - #142

Merged
lich0821 merged 1 commit into
lich0821:masterfrom
kokojacket:fix/long-thinking-streams
Oct 8, 2026
Merged

lich0821 merged 1 commit into
lich0821:masterfrom
kokojacket:fix/long-thinking-streams

Conversation

@kokojacket

Copy link
Copy Markdown
Contributor

Summary

  • size the claude transformer's per-event scanner to the event itself instead of bufio.Scanner's default 64 KiB limit, and pass the original event through if scanning ever fails
  • replace the upstream client's 300s total Timeout with a 10-minute idle timeout (no headers / no body data), so healthy long streams are never cut off while stalled upstreams are still aborted
  • lift the server WriteTimeout (10 min) for the proxy route only, so long streams are not truncated on the client side
  • keep /v1/models fetches bounded by their own 300s timeout, since the shared client no longer has a total timeout

Why

With long extended thinking (e.g. Opus at high effort), Claude Code sessions behind ccNexus broke permanently:

  1. Lost thinking signatures. The signature_delta line grows with thinking length and goes over 64 KiB after roughly 4 minutes of thinking. ClaudeTransformer.TransformResponseWithContext used a default bufio.Scanner and ignored scanner.Err(), so that line was silently dropped (only event: content_block_delta was forwarded). The client stored a thinking block without a signature, and every following request failed with 400 messages.N.content.0.thinking: each thinking block must contain thinking. The session could not continue until the transcript was edited by hand. fix: support multi-megabyte SSE events #141 raised the limit in internal/proxy, but this scanner in internal/transformer/cc still used the default.
  2. Streams cut at exactly 5 minutes. http.Client.Timeout covers reading the response body, so any stream longer than 300s ended with Connection lost mid-response. The server WriteTimeout would cut them off again at 10 minutes.

Tests

  • new TestClaudeTransformerForwardsLargeSignatureDelta (fails before the fix: 27 of 102525 bytes forwarded)
  • new TestProxiedStreamOutlivesServerWriteTimeout: end-to-end through handleProxy, fails before the fix with unexpected EOF
  • new idle-timeout tests: an active stream outlives the idle timeout, and stalled streams and missing headers are aborted
  • go test ./... -count=1 -race

🤖 Generated with Claude Code

- ClaudeTransformer scanned each SSE event with bufio.Scanner's default
  64KB line limit and ignored the error. The signature_delta of long
  thinking blocks exceeds that, so the line was silently dropped and
  clients received thinking blocks without a signature, which the API
  then rejects on every following turn ("each thinking block must
  contain thinking").
- The upstream http.Client had a 300s total Timeout that cut off
  healthy streams mid-response. Replace it with an idle timeout that
  only aborts upstreams sending nothing for 10 minutes.
- Lift the server WriteTimeout (10 min) for the proxy route so long
  streams are not truncated on the client side.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@lich0821
lich0821 merged commit 8fd3ebb into lich0821:master Oct 8, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants