Skip to content

fix(compaction): drop thinking/redacted_thinking blocks for Anthropic models#39940

Open
liangruochong44-ui wants to merge 5 commits intoopenclaw:mainfrom
liangruochong44-ui:fix/39887-compaction-thinking-blocks
Open

fix(compaction): drop thinking/redacted_thinking blocks for Anthropic models#39940
liangruochong44-ui wants to merge 5 commits intoopenclaw:mainfrom
liangruochong44-ui:fix/39887-compaction-thinking-blocks

Conversation

@liangruochong44-ui
Copy link
Copy Markdown

This fixes #39887 where compaction corrupts thinking blocks, causing Anthropic API to reject requests with:

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

Changes

  • Modified dropThinkingBlocks() to also strip redacted_thinking blocks
  • Enabled dropThinkingBlocks for all Anthropic models (including Bedrock)
  • Added tests to verify the fix

Closes #39887

lrc added 5 commits March 8, 2026 22:24
- Add WAL journal mode to memory SQLite database (fixes openclaw#36035)
  WAL mode is crash-safe and survives SIGTERM/SIGKILL mid-write
- Add transient SQLite error handling (fixes openclaw#34678)
  Classify SQLITE_CANTOPEN, SQLITE_BUSY, SQLITE_LOCKED, SQLITE_IOERR as
  non-fatal errors to prevent LaunchAgent crash loops
The cronEvalCache was using FIFO eviction (first inserted = first
evicted), but Map.get() doesn't reorder entries. This caused frequently
accessed entries to be prematurely evicted.

Fix: On cache hit, delete and re-set the entry to move it to the end
of Map iteration order, implementing true LRU behavior.

Fixes openclaw#39679
This fixes the regression where kimi-coding and other non-Anthropic
providers using anthropic-messages API format would crash on turn 2+
because they cannot handle re-sent thinkingSignature blocks.

The original change (preserveSignatures: isAnthropic) was too broad -
it affected all providers with modelApi=anthropic-messages, including
kimi-coding which doesn't support this feature.

Now preserveSignatures only applies to GitHub Copilot Claude, matching
the existing dropThinkingBlocks logic.

Fixes: openclaw#39798
Related: openclaw#39844, openclaw#39839, openclaw#39788
Support rendering secret input fields that use string | {source, provider, id}
union types by using a textarea that accepts both plain strings and JSON objects.

Fixes openclaw#39869
… models

This fixes openclaw#39887 where compaction corrupts thinking blocks, causing
Anthropic API to reject requests with:

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

Changes:
- Modified dropThinkingBlocks() to also strip redacted_thinking blocks
- Enabled dropThinkingBlocks for all Anthropic models (including Bedrock)
- Added tests to verify the fix

Closes openclaw#39887
@openclaw-barnacle openclaw-barnacle bot added app: web-ui App: web-ui agents Agent runtime and tooling size: S labels Mar 8, 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: bff6311191

ℹ️ 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".

Comment on lines +72 to +75
"SQLITE_CANTOPEN",
"SQLITE_BUSY",
"SQLITE_LOCKED",
"SQLITE_IOERR",
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 Treat persistent SQLite open failures as fatal

Classifying SQLITE_CANTOPEN and SQLITE_IOERR as transient causes installUnhandledRejectionHandler to suppress unhandled rejections from permanent database outages (for example invalid store.path, permission-denied database files, or real disk I/O faults). In those cases the process continues in a degraded state with broken persistence instead of failing fast, which makes the outage harder to detect and recover from.

Useful? React with 👍 / 👎.

Comment on lines +667 to +669
try {
const parsed = JSON.parse(raw);
onPatch(path, parsed);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid coercing scalar text into wrong union types

renderUnionTextInput always runs JSON.parse on textarea input, so values that users intend as plain strings (e.g. 123, true, null) are silently converted to number/boolean/null. For string | object unions this breaks the advertised “plain string or JSON object” behavior and can persist the wrong type to config consumers.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 8, 2026

Greptile Summary

This PR fixes compaction-induced API rejections for Anthropic models by (1) extending dropThinkingBlocks() to also strip redacted_thinking blocks, and (2) enabling dropThinkingBlocks for all Anthropic/Bedrock providers — not just GitHub Copilot Claude. The pipeline order (signatures are handled during image sanitization, blocks are dropped after) is correct, and the approach is sound. The PR also bundles three independent improvements: a proper LRU eviction fix for the cron cache, WAL mode for SQLite, and a renderUnionTextInput fallback for mixed-type union fields in the config UI.

Key changes:

  • dropThinkingBlocks() now strips both thinking and redacted_thinking block types.
  • resolveTranscriptPolicy sets dropThinkingBlocks = true for all Anthropic/Bedrock models; preserveSignatures is narrowed to Copilot Claude only (previously it applied to all Anthropic providers). The Copilot preserveSignatures value silently changed from false to true — this is functionally harmless since dropThinkingBlocks removes the blocks before they're sent — but it's undocumented and untested.
  • SQLITE_CANTOPEN is included in the "transient" SQLite code set. Unlike SQLITE_BUSY/SQLITE_LOCKED, this code often reflects a permanent misconfiguration and may suppress important diagnostics when treated as transient.
  • No test covers the Copilot Claude case for either dropThinkingBlocks or preserveSignatures after this refactor.

Confidence Score: 4/5

  • Safe to merge — the core bug fix is correct and well-scoped; secondary concerns are non-blocking.
  • The primary fix (dropping redacted_thinking blocks and enabling dropThinkingBlocks for all Anthropic providers) directly addresses the reported API rejection and is logically sound. The two minor concerns — SQLITE_CANTOPEN being categorised as transient, and the undocumented preserveSignatures flip for Copilot — do not affect the main fix and are unlikely to cause regressions, but they lower confidence slightly.
  • src/infra/unhandled-rejections.ts (SQLITE_CANTOPEN classification) and src/agents/transcript-policy.ts (missing Copilot test coverage).

Last reviewed commit: bff6311

Comment on lines 68 to +74
];

// SQLite error codes that indicate transient failures (shouldn't crash the gateway)
const TRANSIENT_SQLITE_CODES = new Set([
"SQLITE_CANTOPEN",
"SQLITE_BUSY",
"SQLITE_LOCKED",
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.

SQLITE_CANTOPEN is not always transient

SQLITE_CANTOPEN typically means SQLite cannot open the database file due to a wrong path, missing directory, or a permanent permission denial — these won't resolve on their own. Treating it as transient means the gateway will silently swallow and continue past what might be a hard misconfiguration (e.g., wrong store.path), making it much harder to diagnose.

Consider separating it from the genuinely transient codes or at least logging it at a higher severity level than the others:

Suggested change
];
// SQLite error codes that indicate transient failures (shouldn't crash the gateway)
const TRANSIENT_SQLITE_CODES = new Set([
"SQLITE_CANTOPEN",
"SQLITE_BUSY",
"SQLITE_LOCKED",
// SQLite error codes that indicate transient failures (shouldn't crash the gateway)
const TRANSIENT_SQLITE_CODES = new Set([
"SQLITE_BUSY",
"SQLITE_LOCKED",
"SQLITE_IOERR",
]);
// Permanent-ish codes that should still be logged loudly but not crash
const NONFATAL_SQLITE_CODES = new Set([
"SQLITE_CANTOPEN",
]);

At a minimum, the SQLITE_CANTOPEN branch might deserve a console.error rather than console.warn so operators notice it.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/unhandled-rejections.ts
Line: 68-74

Comment:
**`SQLITE_CANTOPEN` is not always transient**

`SQLITE_CANTOPEN` typically means SQLite cannot open the database file due to a wrong path, missing directory, or a permanent permission denial — these won't resolve on their own. Treating it as transient means the gateway will silently swallow and continue past what might be a hard misconfiguration (e.g., wrong `store.path`), making it much harder to diagnose.

Consider separating it from the genuinely transient codes or at least logging it at a higher severity level than the others:

```suggestion
// SQLite error codes that indicate transient failures (shouldn't crash the gateway)
const TRANSIENT_SQLITE_CODES = new Set([
  "SQLITE_BUSY",
  "SQLITE_LOCKED",
  "SQLITE_IOERR",
]);

// Permanent-ish codes that should still be logged loudly but not crash
const NONFATAL_SQLITE_CODES = new Set([
  "SQLITE_CANTOPEN",
]);
```

At a minimum, the `SQLITE_CANTOPEN` branch might deserve a `console.error` rather than `console.warn` so operators notice it.

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

Comment on lines +124 to +126
// Only preserve signatures for GitHub Copilot Claude - other anthropic-messages API
// providers (like kimi-coding) cannot handle re-sent thinkingSignature blocks
const preserveSignatures = isCopilotClaude;
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.

Missing test coverage for Copilot Claude dropThinkingBlocks and preserveSignatures

The PR updates the dropThinkingBlocks flag to be true for all Anthropic models (including Copilot), and changes preserveSignatures to be true only for Copilot (previously it was false for Copilot). Neither of these Copilot-specific outcomes is tested.

The old tests that were removed checked preserveSignatures for provider: "anthropic" and provider: "amazon-bedrock". No test now verifies the Copilot case for either field. Consider adding:

it("drops thinking blocks and preserves signatures for GitHub Copilot Claude", () => {
  const policy = resolveTranscriptPolicy({
    provider: "github-copilot",
    modelId: "claude-sonnet-4-5",
    modelApi: "anthropic-messages",
  });
  expect(policy.dropThinkingBlocks).toBe(true);
  expect(policy.preserveSignatures).toBe(true);
});

The preserveSignatures = true for Copilot is functionally harmless (thinking blocks are dropped by dropThinkingBlocks anyway), but the change is undocumented and untested.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/transcript-policy.ts
Line: 124-126

Comment:
**Missing test coverage for Copilot Claude `dropThinkingBlocks` and `preserveSignatures`**

The PR updates the `dropThinkingBlocks` flag to be `true` for all Anthropic models (including Copilot), and changes `preserveSignatures` to be `true` **only** for Copilot (previously it was `false` for Copilot). Neither of these Copilot-specific outcomes is tested.

The old tests that were removed checked `preserveSignatures` for `provider: "anthropic"` and `provider: "amazon-bedrock"`. No test now verifies the Copilot case for either field. Consider adding:

```ts
it("drops thinking blocks and preserves signatures for GitHub Copilot Claude", () => {
  const policy = resolveTranscriptPolicy({
    provider: "github-copilot",
    modelId: "claude-sonnet-4-5",
    modelApi: "anthropic-messages",
  });
  expect(policy.dropThinkingBlocks).toBe(true);
  expect(policy.preserveSignatures).toBe(true);
});
```

The `preserveSignatures = true` for Copilot is functionally harmless (thinking blocks are dropped by `dropThinkingBlocks` anyway), but the change is undocumented and untested.

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

@coletoncodes
Copy link
Copy Markdown

any updates here?

Copy link
Copy Markdown
Contributor

@xiaoyaner0201 xiaoyaner0201 left a comment

Choose a reason for hiding this comment

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

🔍 Duplicate PR Group: Anthropic Thinking Block Compaction

This PR is one of 7 open PRs addressing the same issue: Anthropic thinking/redacted_thinking blocks from older assistant messages cause API errors during context compaction.

Related PRs

PR Author Date Approach Preserves latest redacted_thinking Compaction path Tests Clean diff
#24261 @Vaibhavee89 02-23 Guard functions + logging N/A (no actual strip) Detect only Medium
#25381 @Nipurn123 02-24 Modify dropThinkingBlocks skip latest Good ❌ (bundles sendDice)
#27142 @slatem 02-26 Extend dropThinkingBlocks to Anthropic ❌ (strips all) Limited
#39919 @taw0002 03-08 New strip function in sanitizeSessionHistory Good
#39940 @liangruochong44-ui 03-08 Policy flag for Anthropic ❌ (strips all) Medium ❌ (bundles cron/SQLite/UI)
#43783 @coletoncodes 03-12 New stripThinkingFromNonLatestAssistant + compact path Best
#44650 @rikisann 03-13 Runtime monkey-patch of node_modules None ⚠️ Fragile approach

Analysis

After reading all 7 diffs:

#43783 is the strongest candidate. It:

  1. Creates a standalone stripThinkingFromNonLatestAssistant() without changing dropThinkingBlocks semantics (preserves Copilot behavior)
  2. Integrates via existing policy.preserveSignatures flag (proper transcript policy mechanism)
  3. Only PR that also fixes the compaction path (compact.ts) — others only fix sanitizeSessionHistory
  4. Handles both thinking and redacted_thinking block types
  5. Clean diff with comprehensive tests (toolResult interleaving, empty blocks, reference equality)

#39919 is a close second (same core function, clean diff, good tests) but misses the compaction path.

Notable discovery from #44650: sanitizeSurrogates may corrupt thinking block signatures — worth investigating separately even though the monkey-patch approach is not viable.

Not recommended: #27142 and #39940 strip thinking from ALL messages including latest (breaks multi-turn quality). #25381 and #39940 bundle unrelated changes.

Similarities identified via embedding-based clustering (cosine > 0.82). Maintainers: #43783 appears ready for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compaction corrupts thinking/redacted_thinking blocks, breaking Anthropic API sessions

3 participants