fix(compaction): drop thinking/redacted_thinking blocks for Anthropic models#39940
fix(compaction): drop thinking/redacted_thinking blocks for Anthropic models#39940liangruochong44-ui wants to merge 5 commits intoopenclaw:mainfrom
Conversation
- 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
There was a problem hiding this comment.
💡 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".
| "SQLITE_CANTOPEN", | ||
| "SQLITE_BUSY", | ||
| "SQLITE_LOCKED", | ||
| "SQLITE_IOERR", |
There was a problem hiding this comment.
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 👍 / 👎.
| try { | ||
| const parsed = JSON.parse(raw); | ||
| onPatch(path, parsed); |
There was a problem hiding this comment.
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 SummaryThis PR fixes compaction-induced API rejections for Anthropic models by (1) extending Key changes:
Confidence Score: 4/5
Last reviewed commit: bff6311 |
| ]; | ||
|
|
||
| // SQLite error codes that indicate transient failures (shouldn't crash the gateway) | ||
| const TRANSIENT_SQLITE_CODES = new Set([ | ||
| "SQLITE_CANTOPEN", | ||
| "SQLITE_BUSY", | ||
| "SQLITE_LOCKED", |
There was a problem hiding this 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:
| ]; | |
| // 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.| // Only preserve signatures for GitHub Copilot Claude - other anthropic-messages API | ||
| // providers (like kimi-coding) cannot handle re-sent thinkingSignature blocks | ||
| const preserveSignatures = isCopilotClaude; |
There was a problem hiding this 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:
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.|
any updates here? |
xiaoyaner0201
left a comment
There was a problem hiding this comment.
🔍 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 |
Analysis
After reading all 7 diffs:
#43783 is the strongest candidate. It:
- Creates a standalone
stripThinkingFromNonLatestAssistant()without changingdropThinkingBlockssemantics (preserves Copilot behavior) - Integrates via existing
policy.preserveSignaturesflag (proper transcript policy mechanism) - Only PR that also fixes the compaction path (
compact.ts) — others only fixsanitizeSessionHistory - Handles both
thinkingandredacted_thinkingblock types - 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.
This fixes #39887 where compaction corrupts thinking blocks, causing Anthropic API to reject requests with:
Changes
dropThinkingBlocks()to also stripredacted_thinkingblocksdropThinkingBlocksfor all Anthropic models (including Bedrock)Closes #39887