Skip to content

fix(agents): preserve thinking/redacted_thinking blocks in stripThoughtSignatures#29620

Closed
iamhitarth wants to merge 1 commit intoopenclaw:mainfrom
iamhitarth:fix/preserve-anthropic-thinking-blocks
Closed

fix(agents): preserve thinking/redacted_thinking blocks in stripThoughtSignatures#29620
iamhitarth wants to merge 1 commit intoopenclaw:mainfrom
iamhitarth:fix/preserve-anthropic-thinking-blocks

Conversation

@iamhitarth
Copy link
Copy Markdown

Summary

  • stripThoughtSignatures removes thought_signature fields from content blocks for cross-provider compatibility (e.g. Gemini)
  • However, it also modifies thinking and redacted_thinking blocks via object spread + delete
  • Anthropic's API requires these blocks to be identical to the original response in the latest assistant message
  • On multi-turn conversations, the modified thinking blocks cause the API to reject with: "thinking or redacted_thinking blocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response."

Fix

Skip type: "thinking" and type: "redacted_thinking" blocks in stripThoughtSignatures — these blocks should never have their signatures stripped since they must be preserved verbatim for Anthropic.

Fixes #29618

Reproduction

  1. Use Anthropic provider with extended thinking enabled
  2. Have a multi-turn conversation in a Discord thread where thinking blocks are stored in session
  3. On a subsequent turn, the session history replay modifies the thinking blocks via sanitizeSessionHistorysanitizeSessionMessagesImagesstripThoughtSignatures
  4. API rejects with the error above

Test plan

  • Verify multi-turn conversations with extended thinking no longer produce "cannot be modified" errors
  • Verify cross-provider signature stripping still works for non-thinking blocks (e.g. text blocks with thought_signature)

🤖 Generated with Claude Code

…htSignatures

stripThoughtSignatures removes thought_signature fields from content
blocks for cross-provider compatibility. However, it also modifies
thinking and redacted_thinking blocks, which Anthropic's API requires
to be identical to the original response. When these modified blocks
appear in the latest assistant message on a subsequent turn, the API
rejects the request with:

  "thinking or redacted_thinking blocks in the latest assistant message
   cannot be modified"

Skip thinking/redacted_thinking block types in stripThoughtSignatures
to preserve them unchanged.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: XS labels Feb 28, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 004e91311b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +71 to +72
if (blockType === "thinking" || blockType === "redacted_thinking") {
return block;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Gate thinking-block bypass to Anthropic requests

Returning early for every thinking/redacted_thinking block disables the allowBase64Only signature sanitization that Gemini paths rely on (resolveTranscriptPolicy sets this for Google/OpenRouter in src/agents/transcript-policy.ts:115-124). In provider-switch sessions (for example, Claude history containing thought_signature: "msg_*"), those non-base64 signatures are now forwarded unchanged, undoing the cross-provider compatibility behavior stripThoughtSignatures is intended to provide and potentially causing Gemini follow-up requests to fail. This bypass should be scoped to Anthropic replay requirements rather than applied unconditionally.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 28, 2026

Greptile Summary

Adds an early return in stripThoughtSignatures to preserve thinking/redacted_thinking blocks unchanged, fixing API rejections from Anthropic which requires these blocks to remain identical to the original response.

Changes:

  • Checks block type before processing
  • Returns thinking/redacted_thinking blocks immediately without modification
  • Preserves object identity and all fields (including thought_signature) for these block types

Impact:

  • Fixes multi-turn conversation errors with extended thinking enabled
  • Changes existing behavior: thinking blocks with msg_* signatures are now preserved instead of stripped (correct per Anthropic requirements)
  • Test file needs updating to reflect new behavior

Confidence Score: 4/5

  • Safe to merge after updating test expectations
  • The fix correctly addresses a real API compatibility issue with clear, efficient logic. The only concern is that an existing test expects the old (incorrect) behavior and needs updating. The change is well-justified by Anthropic's API requirements.
  • Verify test file src/agents/pi-embedded-helpers.sanitizeuserfacingtext.test.ts passes with updated expectations for thinking block preservation

Last reviewed commit: 004e913

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +68 to +73
// Never modify thinking/redacted_thinking blocks — Anthropic requires these
// to be identical to the original API response.
const blockType = (block as { type?: unknown }).type;
if (blockType === "thinking" || blockType === "redacted_thinking") {
return block;
}
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.

test file needs updating - the test at lines 148-160 in src/agents/pi-embedded-helpers.sanitizeuserfacingtext.test.ts expects thinking blocks with msg_* signatures to be stripped, but this fix preserves them unchanged (which is correct per Anthropic's requirements)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-helpers/bootstrap.ts
Line: 68-73

Comment:
test file needs updating - the test at lines 148-160 in `src/agents/pi-embedded-helpers.sanitizeuserfacingtext.test.ts` expects `thinking` blocks with `msg_*` signatures to be stripped, but this fix preserves them unchanged (which is correct per Anthropic's requirements)

How can I resolve this? If you propose a fix, please make it concise.

@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Mar 6, 2026
@AuburyEssentian
Copy link
Copy Markdown

Still relevant — this fix prevents Anthropic API rejections when thinking blocks are passed back in a multi-turn conversation. The stripThoughtSignatures function was stripping/transforming content blocks that must remain byte-identical to the original API response. Happy to rebase or address any feedback.

@AuburyEssentian
Copy link
Copy Markdown

Closing as superseded — upstream merged equivalent fixes for thinking block preservation (strip only msg_* signatures, preserve thinking/redacted_thinking blocks verbatim) in the interim. The bug this addressed is no longer present in current main.

@AuburyEssentian
Copy link
Copy Markdown

Noting that upstream has since merged equivalent fixes for thinking block preservation (stripping only msg_* signatures, preserving thinking/redacted_thinking blocks verbatim). This PR may now be superseded — maintainers, feel free to close if the issue is fully addressed in current main.

a-alphayed added a commit to a-alphayed/openclaw that referenced this pull request Mar 13, 2026
…story sanitization

The Anthropic API requires thinking and redacted_thinking blocks to be
returned byte-for-byte identical in conversation history. Multiple
sanitization functions were mutating these blocks via object spread and
field deletion, causing API rejections with: "thinking or redacted_thinking
blocks in the latest assistant message cannot be modified."

Add isImmutableThinkingBlock() guard and apply it in all four mutation sites:
- stripThoughtSignatures (bootstrap.ts) — was deleting signature fields
- sanitizeToolResult (pi-embedded-subscribe.tools.ts) — was deleting .data
- pruneProcessedHistoryImages (history-image-prune.ts) — was replacing blocks
- dropThinkingBlocks (thinking.ts) — was missing redacted_thinking type

Fixes openclaw#29618
Supersedes openclaw#29620

Co-Authored-By: Claude Opus 4.6 <[email protected]>
a-alphayed added a commit to a-alphayed/openclaw that referenced this pull request Mar 14, 2026
…story sanitization

The Anthropic API requires thinking and redacted_thinking blocks to be
returned byte-for-byte identical in conversation history. Multiple
sanitization functions were mutating these blocks via object spread and
field deletion, causing API rejections with: "thinking or redacted_thinking
blocks in the latest assistant message cannot be modified."

Add isImmutableThinkingBlock() guard and apply it in all four mutation sites:
- stripThoughtSignatures (bootstrap.ts) — was deleting signature fields
- sanitizeToolResult (pi-embedded-subscribe.tools.ts) — was deleting .data
- pruneProcessedHistoryImages (history-image-prune.ts) — was replacing blocks
- dropThinkingBlocks (thinking.ts) — was missing redacted_thinking type

Fixes openclaw#29618
Supersedes openclaw#29620

Co-Authored-By: Claude Opus 4.6 <[email protected]>
a-alphayed added a commit to a-alphayed/openclaw that referenced this pull request Mar 14, 2026
…story sanitization

The Anthropic API requires thinking and redacted_thinking blocks to be
returned byte-for-byte identical in conversation history. Multiple
sanitization functions were mutating these blocks via object spread and
field deletion, causing API rejections with: "thinking or redacted_thinking
blocks in the latest assistant message cannot be modified."

Add isImmutableThinkingBlock() guard and apply it in all four mutation sites:
- stripThoughtSignatures (bootstrap.ts) — was deleting signature fields
- sanitizeToolResult (pi-embedded-subscribe.tools.ts) — was deleting .data
- pruneProcessedHistoryImages (history-image-prune.ts) — was replacing blocks
- dropThinkingBlocks (thinking.ts) — was missing redacted_thinking type

Fixes openclaw#29618
Supersedes openclaw#29620

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@openclaw-barnacle openclaw-barnacle bot removed the stale Marked as stale due to inactivity label Mar 28, 2026
@obviyus
Copy link
Copy Markdown
Contributor

obviyus commented Apr 1, 2026

Closing as duplicate; this was superseded by #58916.

@obviyus obviyus closed this Apr 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(agents): stripThoughtSignatures modifies Anthropic thinking blocks, causing API rejection

3 participants