Skip to content

fix(sse): pass Claude passthrough thinking blocks through unchanged - #3775

Merged
diegosouzapw merged 3 commits into
diegosouzapw:release/v3.8.24from
havockdev:fix/claude-passthrough-thinking-cannot-be-modified
Jun 13, 2026
Merged

diegosouzapw merged 3 commits into
diegosouzapw:release/v3.8.24from
havockdev:fix/claude-passthrough-thinking-cannot-be-modified

Conversation

@havockdev

Copy link
Copy Markdown

Problem

On the Anthropic-native Claude OAuth passthrough, every multi-turn request that carries extended thinking fails with:

[400]: messages.N.content.M: `thinking` or `redacted_thinking` blocks in the latest
assistant message cannot be modified. These blocks must remain as they were in the
original response.

It is especially visible on long Claude Code tool-loops (the conversation accumulates many thinking turns, so it reproduces on almost every follow-up request).

Root cause

redactPassthroughThinkingSignatures (open-sse/handlers/chatCore.ts) rewrites every assistant thinking/redacted_thinking block to redacted_thinking{data:<synthetic>} before forwarding to Anthropic.

The Messages API validates submitted thinking blocks against the original response and rejects any modification — so converting them is exactly what triggers the cannot be modified 400.

The rewrite was added to avoid a presumed 400 "Invalid signature in thinking block" (#2454) on a token/model switch, on the assumption that the signature is bound to the auth token that produced it. In practice the thinking-block signature is validated server-side by Anthropic and stays valid on replay — including under a different OAuth token, which I verified by preserving the blocks across a mid-conversation account switch with zero Invalid signature responses. So the redaction is both unnecessary and the cause of this regression.

Fix

Pass the thinking/redacted_thinking blocks through unchanged (the function now returns messages as-is). The signature parameter is kept for call-site compatibility, and the doc-comment is updated to explain the two errors and why the redaction was removed.

Testing

  • Updated tests/unit/claude-passthrough-thinking-2454.test.ts to assert the blocks pass through unchanged (historical and latest), pre-existing redacted_thinking is not re-stamped, and non-array input passes through. Run:
    node --import tsx/esm --test tests/unit/claude-passthrough-thinking-2454.test.ts
    
    → 3/3 pass.
  • Validated in production: multi-turn Claude Code sessions with extended thinking (long tool-loops) no longer return the 400, including across a mid-conversation OAuth account switch with no Invalid signature.

Checklist

  • Changed unit test included (tests/unit/claude-passthrough-thinking-2454.test.ts)
  • prettier --check clean on changed files; eslint clean on changed files
  • No hardcoded secrets / no raw error bodies touched
  • CHANGELOG updated under [Unreleased]
  • No Co-Authored-By trailers

diegosouzapw and others added 3 commits June 13, 2026 00:07
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.28.0 to 0.28.1.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](evanw/esbuild@v0.28.0...v0.28.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Rewriting assistant `thinking` blocks to `redacted_thinking` made the Anthropic
Messages API reject multi-turn requests with extended thinking:

  400 messages.N.content.M: `thinking` or `redacted_thinking` blocks in the
  latest assistant message cannot be modified.

Submitted thinking blocks are validated against the original response, so any
rewrite is rejected. This broke every multi-turn request with thinking on the
Anthropic-native Claude OAuth passthrough (most visible on long Claude Code
tool-loops).

The thinking-block signature is validated server-side and stays valid on replay,
including under a different OAuth token, so the redaction added for diegosouzapw#2454 is
unnecessary. Pass the blocks through unchanged.
@havockdev
havockdev requested a review from diegosouzapw as a code owner June 13, 2026 14:42

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request stops rewriting assistant thinking blocks to redacted_thinking in the Claude OAuth passthrough, resolving a 400 error where the Messages API rejected modified thinking blocks. The redactPassthroughThinkingSignatures function is now a no-op that returns the messages unchanged, and unit tests have been updated accordingly. The reviewer suggested marking this now-obsolete function as @deprecated in its JSDoc to signal that it should be refactored out of call sites.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

* responses. The redaction is therefore both unnecessary and the cause of the
* regression, so the blocks are now returned verbatim. The `signature` parameter
* is kept for call-site compatibility.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since redactPassthroughThinkingSignatures is now a no-op that returns the input messages unchanged, it is highly recommended to mark this function as @deprecated in the JSDoc. This informs other developers and IDEs that the function is obsolete and should be refactored out of call sites in future cleanups.

Suggested change
*/
*\n * @deprecated This function is now a no-op and returns the messages unchanged.\n * Callers should be refactored to avoid calling this function.\n */

@diegosouzapw

Copy link
Copy Markdown
Owner

✅ Live validation (Rule #18) — confirmed against the real Anthropic Messages API

Validated the core claim directly against api.anthropic.com/v1/messages (model claude-sonnet-4-6, extended thinking enabled):

  1. Generate a real thinking block → 200, block carries a 332-char signature.
  2. Replay the thinking block VERBATIM (this PR's behavior — pass blocks through unchanged) → 200 ACCEPTED, and crucially no Invalid signature in thinking block (fix: Sanitize Claude Thinking Block Signatures & Beta Headers in Proxy Passthrough #2454). The signature validates on replay, refuting the original premise that it is auth-token-bound.
  3. Replay with the block rewritten to redacted_thinking (the current release behavior, redactPassthroughThinkingSignatures → synthetic data) → 400 REJECTED: messages.N.content.0: Invalid \data` in `redacted_thinking` block`.

Result: the verbatim passthrough this PR restores is accepted; the redaction it removes is rejected by Anthropic. The fix resolves a real multi-turn extended-thinking breakage and does not reintroduce #2454.

Minor note: the observed 400 string was Invalid \data` in `redacted_thinking`rather than the exactcannot be modified wording in the CHANGELOG — same root cause (the redaction is rejected), just a different Anthropic error message for the synthetic-data` case. Optional CHANGELOG wording tweak, non-blocking.

Merging into release/v3.8.24. Thanks @havockdev! 🙌

@diegosouzapw
diegosouzapw changed the base branch from main to release/v3.8.24 June 13, 2026 16:42
@diegosouzapw
diegosouzapw merged commit c0a57d2 into diegosouzapw:release/v3.8.24 Jun 13, 2026
1 of 2 checks passed
diegosouzapw added a commit that referenced this pull request Jun 13, 2026
Resolve file-size-baseline.json conflict in the _rebaseline keys block: keep this PR's combo_quota_audit key + release's v3824_3776 key (#3776); drop the now-redundant drift_3780 key (release's #3776 key already documents the base.ts 1205->1218 carry-over from #3780). frozen values auto-merged (combo.ts 5131 mine, base.ts 1218 both, chatCore.ts 5808 theirs). Verified on the merged tree: all 14 Fast Quality Gates pass (incl. file-size: chatCore 5736<=5808, combo 5131<=5131), W1 streaming hook intact, quota-streaming 8 / complexity-router 6 / scoring-clamp 7 / combo-routing-engine 81 green. Pre-existing release app-tsc noise in chatCore (3026-4380, from #3775) is untouched by this PR.
diegosouzapw added a commit that referenced this pull request Jun 13, 2026
…contributor credits

- Restructure [3.8.24] into ✨ Features / 🔒 Security / 🐛 Fixed / 📝 Maintenance
- Add bullets for every PR landed since v3.8.23 that was missing:
  marketplace (#3656), strict-mode CC defaults (#3776), emergency-fallback flag (#3752),
  xhigh effort (#3756), Codex memory WS (#3749), IPv6 egress (#3777),
  marketplace SSRF (#3774), CodeQL/Dependabot (#3778), anthropic sampling (#3780),
  thinking passthrough (#3775), mcp dist entry (#3765), streamed tool args (#3762),
  logs light-mode (#3760), clean-history purge (#3751), quality-gates (#3757),
  docs gaps (#3453), file-size re-baseline (#3770), E415 publish guard, i18n prune
- Move misplaced #3775 bullet out of [Unreleased] into [3.8.24]
- Date [3.8.23] header (TBD -> 2026-06-12, the release tag date)
@diegosouzapw diegosouzapw mentioned this pull request Jun 13, 2026
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…iegosouzapw#3775)

Pass Claude passthrough thinking blocks through unchanged (fixes the redacted_thinking 400). Live-validated against the Anthropic API. Integrated into release/v3.8.24.
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…contributor credits

- Restructure [3.8.24] into ✨ Features / 🔒 Security / 🐛 Fixed / 📝 Maintenance
- Add bullets for every PR landed since v3.8.23 that was missing:
  marketplace (diegosouzapw#3656), strict-mode CC defaults (diegosouzapw#3776), emergency-fallback flag (diegosouzapw#3752),
  xhigh effort (diegosouzapw#3756), Codex memory WS (diegosouzapw#3749), IPv6 egress (diegosouzapw#3777),
  marketplace SSRF (diegosouzapw#3774), CodeQL/Dependabot (diegosouzapw#3778), anthropic sampling (diegosouzapw#3780),
  thinking passthrough (diegosouzapw#3775), mcp dist entry (diegosouzapw#3765), streamed tool args (diegosouzapw#3762),
  logs light-mode (diegosouzapw#3760), clean-history purge (diegosouzapw#3751), quality-gates (diegosouzapw#3757),
  docs gaps (diegosouzapw#3453), file-size re-baseline (diegosouzapw#3770), E415 publish guard, i18n prune
- Move misplaced diegosouzapw#3775 bullet out of [Unreleased] into [3.8.24]
- Date [3.8.23] header (TBD -> 2026-06-12, the release tag date)
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