Description
Summary
repairToolPairs() partially mutates assistant messages that contain thinking blocks, causing Anthropic to reject the request with a 400 error. This manifests on conversations long enough for OpenCode's compaction to run.
Context
Today (June 30, 2026), Anthropic launched Claude Sonnet 5 as the default model across all plans: https://www.anthropic.com/news/claude-sonnet-5
Sonnet 5 uses adaptive thinking always on (effort: high by default). Every assistant response includes thinking blocks. Combined with OpenCode's automatic compaction (which prunes old messages and creates orphaned tool_use blocks), repairToolPairs() corrupts the thinking-block contract.
Error
messages.1.content.11: `thinking` or `redacted_thinking` blocks in the latest
assistant message cannot be modified. These blocks must remain as they were
in the original response.
Root cause
transformBody() calls repairToolPairs() on every request. The function scans all messages, finds orphaned tool_use / tool_result blocks and removes them from content[]:
const filtered = message.content.filter((block) => {
// orphaned tool_use → false (REMOVED)
// orphaned tool_result → false (REMOVED)
return true; // thinking blocks pass through untouched
});
return { ...message, content: filtered }; // ← new content[] = partial mutation
When an assistant message was originally [thinking, text, tool_use_1, tool_use_2] and its tool_result blocks were dropped by compaction, repairToolPairs() prunes the tool_use blocks but keeps the thinking blocks. The resulting content[] is [thinking, text] — a partial modification.
Anthropic's contract allows two things:
- returning thinking blocks completely unmodified ✅
- omitting them entirely from earlier turns ✅
- but NOT partially rewriting the
content[] that holds them ❌
Affected versions
Reproduced on 2.1.4 and 2.0.1 — repairToolPairs() exists in both. Downgrading does not help.
Suggested fix
When an assistant message contains thinking blocks and has tool_use blocks that would be pruned, omit the entire message instead of partially rewriting its content[] (Anthropic explicitly allows this):
// In repairToolPairs(), before the content filter:
const hasThinking = message.role === "assistant" && message.content.some(
b => b.type === "thinking" || b.type === "redacted_thinking"
);
const hasToolUseToRemove = message.content.some((block) =>
block.type === "tool_use" && typeof block.id === "string" && orphanedUses.has(block.id)
);
if (hasThinking && hasToolUseToRemove) {
return null; // omit entire turn — allowed by Anthropic
}
…then filter out the null entries. Omitting the full turn is explicitly permitted; mutating it is not.
Workaround (for users hitting this now)
Disable automatic compaction temporarily:
"compaction": { "auto": false, "prune": false }
This prevents orphan generation but comes at the cost of unbounded context growth. Not a solution, just a stopgap.
Related
Steps to reproduce
- Use
claude-sonnet-5 (adaptive thinking always on)
- Run an agentic session with several tool calls until compaction triggers
- Send another message → 400 Bad Request
Operating system
Linux
Plugin version
2.1.4
OpenCode version
No response
Debug logs
Description
Summary
repairToolPairs()partially mutates assistant messages that containthinkingblocks, causing Anthropic to reject the request with a 400 error. This manifests on conversations long enough for OpenCode's compaction to run.Context
Today (June 30, 2026), Anthropic launched Claude Sonnet 5 as the default model across all plans: https://www.anthropic.com/news/claude-sonnet-5
Sonnet 5 uses adaptive thinking always on (
effort: highby default). Every assistant response includesthinkingblocks. Combined with OpenCode's automatic compaction (which prunes old messages and creates orphanedtool_useblocks),repairToolPairs()corrupts the thinking-block contract.Error
Root cause
transformBody()callsrepairToolPairs()on every request. The function scans all messages, finds orphanedtool_use/tool_resultblocks and removes them fromcontent[]:When an assistant message was originally
[thinking, text, tool_use_1, tool_use_2]and itstool_resultblocks were dropped by compaction,repairToolPairs()prunes thetool_useblocks but keeps thethinkingblocks. The resultingcontent[]is[thinking, text]— a partial modification.Anthropic's contract allows two things:
content[]that holds them ❌Affected versions
Reproduced on
2.1.4and2.0.1—repairToolPairs()exists in both. Downgrading does not help.Suggested fix
When an assistant message contains thinking blocks and has
tool_useblocks that would be pruned, omit the entire message instead of partially rewriting itscontent[](Anthropic explicitly allows this):…then filter out the
nullentries. Omitting the full turn is explicitly permitted; mutating it is not.Workaround (for users hitting this now)
Disable automatic compaction temporarily:
This prevents orphan generation but comes at the cost of unbounded context growth. Not a solution, just a stopgap.
Related
compaction.auto/compaction.pruneSteps to reproduce
claude-sonnet-5(adaptive thinking always on)Operating system
Linux
Plugin version
2.1.4
OpenCode version
No response
Debug logs