-
-
Notifications
You must be signed in to change notification settings - Fork 7
fix(proxy): prioritize client signature over cache to match Anthropic's exact signature #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Caution Review failedFailed to post review comments 📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a modular analysis-and-processing path for assistant content thinking/tool_use blocks: per-block analysis, signature resolution/caching, optional in-place (surgical) signature updates, block dropping/reordering, and non‑streaming handling; updates tests and renames the internal version package to Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
97fd66d to
486a0ca
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes an Anthropic API error ("thinking blocks in the latest assistant message cannot be modified") by implementing two key changes: (1) prioritizing client-provided signatures over cached signatures to match Anthropic's exact signature expectations, and (2) introducing a "surgical update" approach that preserves unknown fields in thinking blocks byte-for-byte rather than reconstructing them from scratch.
Changes:
- Refactored signature resolution to prioritize client signatures first, then cache as fallback (reversed from previous cache-first approach)
- Added surgical update optimization that only modifies signature fields in-place, preserving all other fields like 'data' and 'redacted_thinking'
- Extracted analysis logic into separate functions to reduce cognitive complexity
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/proxy/thinking.go | Implements signature priority change and surgical update approach with new helper functions (analyzeContentBlocks, surgicalUpdate, resolveThinkingSignature) |
| internal/proxy/thinking_test.go | Adds regression test verifying unknown fields are preserved in thinking blocks |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func TestProcessRequestThinkingPreservesUnknownFields(t *testing.T) { | ||
| // Regression test for Anthropic API error: | ||
| // "thinking blocks in the latest assistant message cannot be modified" | ||
| // The proxy was reconstructing thinking blocks from scratch, losing fields like | ||
| // 'data' and 'redacted_thinking' that Anthropic now requires to be preserved. | ||
| ctx := context.Background() | ||
|
|
||
| sig := "valid_signature_that_is_definitely_long_enough_for_validation" | ||
|
|
||
| // Thinking block with additional fields that must be preserved | ||
| body := `{ | ||
| "model": "claude-sonnet-4", | ||
| "messages": [ | ||
| { | ||
| "role": "assistant", | ||
| "content": [ | ||
| { | ||
| "type": "thinking", | ||
| "thinking": "Some thinking...", | ||
| "signature": "` + sig + `", | ||
| "data": {"some": "metadata", "tokens": 1234} | ||
| }, | ||
| { | ||
| "type": "thinking", | ||
| "thinking": "Redacted thinking...", | ||
| "signature": "` + sig + `", | ||
| "redacted_thinking": true | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| }` | ||
|
|
||
| modifiedBody, thinkingCtx, err := ProcessRequestThinking(ctx, []byte(body), "claude-sonnet-4", nil) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, 0, thinkingCtx.DroppedBlocks) | ||
|
|
||
| // Verify first thinking block preserves 'data' field | ||
| dataField := gjson.GetBytes(modifiedBody, "messages.0.content.0.data") | ||
| assert.True(t, dataField.Exists(), "data field should be preserved") | ||
| assert.Equal(t, "metadata", dataField.Get("some").String(), "data.some should be preserved") | ||
| assert.Equal(t, float64(1234), dataField.Get("tokens").Float(), "data.tokens should be preserved") | ||
|
|
||
| // Verify second thinking block preserves 'redacted_thinking' field | ||
| redactedField := gjson.GetBytes(modifiedBody, "messages.0.content.1.redacted_thinking") | ||
| assert.True(t, redactedField.Exists(), "redacted_thinking field should be preserved") | ||
| assert.True(t, redactedField.Bool(), "redacted_thinking should be true") | ||
| } |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR title mentions "prioritize client signature over cache to match Anthropic's exact signature", but there's no test coverage for this critical behavior change. Consider adding a test that:
- Sets up a cache with one signature for a thinking block
- Sends a request with the same thinking text but a DIFFERENT valid client-provided signature
- Verifies that the client signature is used (not the cached one)
This would ensure the priority order in resolveThinkingSignature (lines 283-318 in thinking.go) is working correctly.
internal/proxy/thinking.go
Outdated
| } | ||
|
|
||
| needsDrop := keptCount < analysis.totalBlocks | ||
| needsReorder := !needsDrop && checkReorderNeeded(analysis.blockTypes) |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a logic bug when both drops and reordering are needed. The condition needsReorder := !needsDrop && checkReorderNeeded(analysis.blockTypes) on line 228 means that if any blocks are dropped, reordering is never performed, even if the remaining blocks still need to be reordered.
Example scenario:
- Input:
[{"type": "text"}, {"type": "thinking", "signature": ""}, {"type": "thinking", "signature": "valid_sig..."}] - After dropping unsigned thinking block:
[{"type": "text"}, {"type": "thinking", "signature": "valid_sig..."}] - Expected:
[{"type": "thinking", "signature": "valid_sig..."}, {"type": "text"}](thinking should come first) - Actual:
[{"type": "text"}, {"type": "thinking", "signature": "valid_sig..."}](not reordered)
The old code checked reordering AFTER collection (using collector.modifiedTypes), but the new code checks BEFORE collection (using analysis.blockTypes) and short-circuits when drops are needed.
Fix: Either always reorder in rebuildContent regardless of needsReorder, or calculate reordering need based on the collected blocks rather than original blocks.
| needsReorder := !needsDrop && checkReorderNeeded(analysis.blockTypes) | |
| needsReorder := checkReorderNeeded(analysis.blockTypes) |
486a0ca to
f240e27
Compare
f240e27 to
ac073e5
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #90 +/- ##
==========================================
- Coverage 78.30% 78.15% -0.15%
==========================================
Files 94 94
Lines 5743 5800 +57
==========================================
+ Hits 4497 4533 +36
- Misses 961 980 +19
- Partials 285 287 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|



No description provided.