Skip to content

fix(agents): preserve thinking/redacted_thinking blocks in session history#44827

Open
a-alphayed wants to merge 5 commits intoopenclaw:mainfrom
a-alphayed:fix/preserve-immutable-thinking-blocks
Open

fix(agents): preserve thinking/redacted_thinking blocks in session history#44827
a-alphayed wants to merge 5 commits intoopenclaw:mainfrom
a-alphayed:fix/preserve-immutable-thinking-blocks

Conversation

@a-alphayed
Copy link
Copy Markdown

@a-alphayed a-alphayed commented Mar 13, 2026

Summary

  • The Anthropic API requires thinking and redacted_thinking blocks in assistant messages to be returned byte-for-byte identical when replayed in conversation history
  • Multiple sanitization functions in the session history pipeline were mutating these blocks via object spread and field deletion, causing API rejections on multi-turn conversations with extended thinking enabled
  • Adds a shared isImmutableThinkingBlock() guard and applies it across all four mutation sites, not just stripThoughtSignatures

Root Cause

Four functions in the sanitization pipeline modify content blocks before sending them back to the Anthropic API:

Function File Mutation
stripThoughtSignatures pi-embedded-helpers/bootstrap.ts Spread-copies blocks and deletes thought_signature fields
sanitizeToolResult pi-embedded-subscribe.tools.ts Spread-copies blocks and deletes .data field (breaks redacted_thinking encrypted payload)
pruneProcessedHistoryImages pi-embedded-runner/run/history-image-prune.ts Replaces content blocks in-place
dropThinkingBlocks pi-embedded-runner/thinking.ts Only checked type === "thinking", missed redacted_thinking

Fix

  • New shared guard: isImmutableThinkingBlock(block) returns true for thinking and redacted_thinking types
  • Each mutation site now short-circuits and returns thinking blocks as-is
  • dropThinkingBlocks now also handles redacted_thinking blocks

Reproduction

  1. Enable extended thinking on Anthropic provider
  2. Have a multi-turn conversation (e.g. Discord thread or Telegram)
  3. On subsequent turns, session history replay triggers sanitizeSessionHistory pipeline
  4. API rejects 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."

Test plan

  • New tests for isImmutableThinkingBlock guard (5 cases)
  • New test for dropThinkingBlocks with redacted_thinking blocks
  • All existing thinking tests pass
  • Build passes
  • Verify multi-turn extended thinking conversations no longer produce the error

Fixes #29618
Supersedes #29620

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: S labels Mar 13, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR fixes a bug where multi-turn conversations with Anthropic's extended thinking feature would fail on replay because four sanitization functions in the session history pipeline were mutating thinking and redacted_thinking content blocks — which the API requires to be returned byte-for-byte identical.

The fix introduces a shared isImmutableThinkingBlock() guard in a new thinking-guard.ts module and applies it consistently across all four mutation sites: stripThoughtSignatures, sanitizeToolResult, pruneProcessedHistoryImages, and dropThinkingBlocks. The approach is clean and well-structured.

Key points:

  • The new guard is well-documented with the exact API error message and is correctly implemented for both thinking and redacted_thinking types.
  • All four mutation sites are correctly protected: spread-and-delete in stripThoughtSignatures, .data field deletion in sanitizeToolResult, in-place replacement in pruneProcessedHistoryImages, and the type filter in dropThinkingBlocks.
  • dropThinkingBlocks now also drops redacted_thinking blocks, which was a pre-existing gap.
  • The JSDoc comment on dropThinkingBlocks (line 15 of thinking.ts) still only mentions "thinking" and should be updated to include "redacted_thinking" now that the function handles both types.
  • Test coverage is solid: 5 unit tests for the new guard and a new integration-style test for dropThinkingBlocks with mixed block types.

Confidence Score: 5/5

  • This PR is safe to merge — the changes are well-targeted, non-breaking, and correctly fix a documented API requirement.
  • The fix is minimal and surgical: a new two-line guard function applied at four clearly identified mutation sites. All changes are additive (early-returns / continue statements), meaning no existing logic paths are altered for non-thinking blocks. The new tests confirm the guard's behavior across edge cases. The only finding is a trivial outdated doc comment.
  • No files require special attention. The single style note is the outdated JSDoc on dropThinkingBlocks in thinking.ts.

Comments Outside Diff (1)

  1. src/agents/pi-embedded-runner/thinking.ts, line 15 (link)

    Outdated JSDoc comment

    The function now drops both thinking and redacted_thinking blocks, but the doc comment on line 15 still only mentions type: "thinking". This could mislead future readers into thinking redacted_thinking blocks are only dropped incidentally.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/thinking.ts
Line: 15

Comment:
**Outdated JSDoc comment**

The function now drops both `thinking` and `redacted_thinking` blocks, but the doc comment on line 15 still only mentions `type: "thinking"`. This could mislead future readers into thinking `redacted_thinking` blocks are only dropped incidentally.

```suggestion
 * Strip all `type: "thinking"` and `type: "redacted_thinking"` content blocks
 * from assistant messages.
```

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

Last reviewed commit: b9acea5

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: b9acea5c19

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@a-alphayed a-alphayed force-pushed the fix/preserve-immutable-thinking-blocks branch 2 times, most recently from 1363e24 to 897a2b8 Compare March 14, 2026 20:32
@openclaw-barnacle openclaw-barnacle bot added the gateway Gateway runtime label Mar 14, 2026
a-alphayed and others added 5 commits March 14, 2026 16:35
…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]>
The isImmutableThinkingBlock guard in stripThoughtSignatures was preventing
signature stripping on non-Anthropic paths (Gemini), where thought_signature
fields must be removed before replaying history. The existing early-return
(!stripSnake && !stripCamel) already handles the no-op case correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@a-alphayed a-alphayed force-pushed the fix/preserve-immutable-thinking-blocks branch from 897a2b8 to 7c1d603 Compare March 14, 2026 23:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling gateway Gateway runtime size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant