← Back to PRs

#22270: fix: add auto-recovery for thinking block immutability errors

by MunemHashmi open 2026-02-20 23:41 View on GitHub →
agents size: M
Closes #22233 ## Problem The Anthropic API requires that `thinking` and `redacted_thinking` blocks in the latest assistant message are sent back **byte-for-byte unchanged**, including the `thinkingSignature` field. When session sanitizers strip or modify those blocks, the API rejects the request with an `invalid_request_error`: > *"thinking or redacted_thinking blocks in the messages cannot be modified"* Before this PR there was no handler for this error, so it surfaced as an unhandled exception with no user-friendly guidance. Unattended channel sessions (Telegram, Discord, etc.) would stay permanently broken until someone manually ran `/new` or `/reset`. --- ## Root Cause `resolveTranscriptPolicy` set `preserveSignatures: false` for standard Anthropic models — only Antigravity Claude had it set to `true`. This caused `sanitizeSessionHistory` to pass thinking blocks through `stripThoughtSignatures`, which could strip or modify the `thinkingSignature` field before the session history was submitted to the API, violating the immutability requirement for the latest assistant turn. --- ## Changes ### `src/agents/pi-embedded-helpers/errors.ts` Added `isThinkingImmutabilityError(raw: string): boolean` — a regex-based classifier that detects the Anthropic thinking block immutability error message. ### `src/agents/pi-embedded-helpers.ts` Re-exported `isThinkingImmutabilityError` from the barrel file. ### `src/agents/pi-embedded-runner/types.ts` Added `"thinking_immutability"` to the `EmbeddedPiRunMeta` error kind union so the new kind is type-safe throughout the codebase. ### `src/agents/transcript-policy.ts` — root cause fix Changed `preserveSignatures` from `isAntigravityClaudeModel` to `isAnthropic || isAntigravityClaudeModel`. This ensures `sanitizeSessionHistory` never passes thinking blocks through `stripThoughtSignatures` for standard Anthropic models, preventing the `thinkingSignature` from being stripped before the session history is sent to the API. ### `src/agents/pi-embedded-runner/run.ts` — auto-recovery handler When the immutability error is detected inside the `promptError` branch of `runEmbeddedPiAgent`, the handler: 1. **Auto-resets** — clears the session file and retries with a fresh session, keeping unattended channel sessions (Telegram, Discord, etc.) alive without manual intervention. Mirrors the pattern used by the compaction recovery handler. 2. **Single-attempt guard** — the reset is only attempted once per run via a `thinkingImmutabilityResetAttempted` flag, preventing infinite retry loops if the error persists after the reset. 3. **Graceful fallback** — if the session file path is empty or cannot be cleared, returns a user-facing error payload with `meta.error.kind: "thinking_immutability"` and guidance to `/new` or `/reset`. --- ## Tests Added | File | Tests | What is covered | |---|---|---| | `errors.isThinkingImmutabilityError.test.ts` | 7 | Empty string, exact Anthropic error message, embedded in longer payload, case-insensitivity, false positives for role ordering / context overflow / generic errors | | `run.thinking-immutability.test.ts` | 4 | Auto-reset clears session file and retries, fallback when sessionFile is empty, single-attempt guard prevents infinite retry, unrelated prompt errors are unaffected | | `pi-embedded-runner.sanitize-session-history.test.ts` | 1 | Asserts `preserveSignatures: true` is passed for `anthropic-messages` provider | | `run.overflow-compaction.mocks.shared.ts` | — | Added `isThinkingImmutabilityError` to the shared `vi.mock` so existing tests remain correct | --- ## Test Plan - [ ] All new tests pass - [ ] Existing overflow compaction and usage reporting tests unaffected - [ ] `tsgo --noEmit` reports no type errors - [ ] `oxfmt` formatting check passes - [ ] `oxlint` reports no lint errors - [ ] Session with a thinking block immutability error auto-resets and retries instead of crashing - [ ] Unattended channel sessions recover automatically without manual `/new` or `/reset` - [ ] If auto-reset fails, user receives a clear error message with recovery instructions

Most Similar PRs