Issue Title
Bug: Subagent announce-delivery echo messages inherit wrong provider/model metadata, causing persistent "thinking blocks cannot be modified" errors after gateway restart
GitHub Issue
Bug: Echo assistant messages from subagent announce-delivery bypass isTranscriptOnlyOpenClawAssistantMessage filter, causing persistent Anthropic API errors after gateway restart
Summary
When a gateway restart occurs while a session has active thinking blocks, the subagent announce-delivery mechanism writes echo assistant messages to the parent session that inherit the original API response metadata (provider="anthropic", model="claude-sonnet-4-6"). Because these messages are not tagged as internal echoes (provider="openclaw", model="delivery-mirror"), they bypass the existing isTranscriptOnlyOpenClawAssistantMessage filter and appear as real consecutive assistant turns in subsequent API requests. This causes Anthropic to reject every subsequent user message with "thinking or redacted_thinking blocks in the latest assistant message cannot be modified" — an error that repeats on every turn for up to 23 minutes until the offending thinking block naturally rotates out of the message window.
Steps to Reproduce
- Enable thinking in a session:
/thinking medium
- Spawn one or more subagents that will deliver results back to the parent session.
- While a turn is in progress (subagent running), restart the OpenClaw gateway.
- After the gateway comes back, send any user message.
- Observe the error immediately:
LLM request rejected: messages.25.content.1: thinking or redacted_thinking blocks
in the latest assistant message cannot be modified. These blocks must remain as
they were in the original response.
- Send another message. The error fires again. Repeat for every message for approximately 20–23 minutes.
Expected Behavior
Subagent announce-delivery echo messages should be transparently filtered from the API message replay. The session transcript should include them as internal bookkeeping entries only, invisible to the Anthropic API, with no impact on the conversation flow. Users should be able to send messages immediately after a gateway restart without error.
Actual Behavior
Every user message after the gateway restart triggers the Anthropic "thinking or redacted_thinking blocks cannot be modified" error. The error self-resolves only once the offending thinking block rotates out of the active message context window (~23 minutes, 8 failed attempts observed in a single session).
Root Cause Analysis
The failure is a chain of three distinct issues. RC-1 is the root cause; RC-2 and RC-3 amplify the damage.
RC-1 (Root of Roots): Echo messages are tagged with the wrong provider/model
When the announce-delivery path writes an echo of the assistant response to the parent session, it copies the original API response metadata verbatim. The resulting session entries look like this (from live session 38ff0e59):
{"id": "8a80757e", "message": {"role": "assistant", "provider": "anthropic", "model": "claude-sonnet-4-6", "api": "anthropic-messages", "content": [{"type": "text", "text": "..."}]}}
{"id": "7e1f9a8e", "message": {"role": "assistant", "provider": "anthropic", "model": "claude-sonnet-4-6", "api": "anthropic-messages", "content": [{"type": "text", "text": "..."}]}}
An existing filter (isTranscriptOnlyOpenClawAssistantMessage) is specifically designed to identify internal echo messages and exclude them from API replay:
// selection-DZnXuqgh.js / normalizeAssistantReplayContent
function isTranscriptOnlyOpenClawAssistantMessage(message) {
const provider = normalizeOptionalString(message.provider) ?? "";
const model = normalizeOptionalString(message.model) ?? "";
return provider === "openclaw" && (model === "delivery-mirror" || model === "gateway-injected");
}
Because the echo messages carry provider="anthropic", this predicate returns false. The echoes pass through as real assistant turns, creating consecutive assistant turns in the API payload — with the earlier turn containing a thinking block that Anthropic refuses to allow in any position other than the final assistant message.
RC-2 (Amplifier): validateAnthropicTurns does not merge consecutive assistant turns
The Gemini validation path already handles consecutive assistant turns correctly:
// pi-embedded-helpers-seljzB3q.js
function validateGeminiTurns(messages) {
return validateTurnsWithConsecutiveMerge({
messages,
role: "assistant", // merges consecutive assistant turns
merge: mergeConsecutiveAssistantTurns,
});
}
The Anthropic path does not:
function validateAnthropicTurns(messages) {
return validateTurnsWithConsecutiveMerge({
messages: stripDanglingAnthropicToolUses(messages),
role: "user", // only merges user turns — no assistant merge
merge: mergeConsecutiveUserTurns,
});
}
mergeConsecutiveAssistantTurns exists and is battle-tested. It is simply not wired into the Anthropic path. If it were, a leaking echo that slips past RC-1 would be silently merged rather than triggering an API error.
RC-3 (Amplifier): Recovery is per-request, not per-session
wrapAnthropicStreamWithRecovery fires when it detects a thinking-block error, strips the thinking blocks, and retries. However, recoveredAnthropicThinking is a per-request flag. The next user message creates a fresh request object with recoveredAnthropicThinking = false. The same error fires, recovery fires again, and this cycle repeats N times until the original thinking block organically scrolls past position messages[25].
This is not the proximate cause, but it is what turns a single structural bug into a 23-minute outage.
Proposed Fixes
Fix 1 — PRIMARY: Tag echo messages correctly at write time
Location: The announce-delivery message writer (wherever it appends the echo assistant message to the parent session transcript).
Change:
// Before writing the echo to the parent session, override metadata:
const echoMessage = {
...originalAssistantMessage,
provider: "openclaw", // was: inherited "anthropic"
model: "delivery-mirror", // was: inherited "claude-sonnet-4-6"
api: undefined, // clear — this is not an API response
};
// write echoMessage to session, not originalAssistantMessage
Why this is safe: The delivery-mirror model value and the isTranscriptOnlyOpenClawAssistantMessage filter are already present in production. This fix simply ensures the tag is applied at write time instead of relying on external consumers to detect echoes by other means. Risk: LOW.
Fix 2 — DEFENSE IN DEPTH: Merge consecutive assistant turns in validateAnthropicTurns
Location: src/agents/pi-embedded-helpers/turns.ts (or equivalent bundled source)
Change:
export function validateAnthropicTurns(messages: AgentMessage[]): AgentMessage[] {
// Step 1: strip dangling tool uses (existing)
const stripped = stripDanglingAnthropicToolUses(messages);
// Step 2: merge consecutive assistant turns (NEW — mirrors validateGeminiTurns)
const withMergedAssistant = validateTurnsWithConsecutiveMerge({
messages: stripped,
role: "assistant",
merge: mergeConsecutiveAssistantTurns,
});
// Step 3: merge consecutive user turns (existing)
return validateTurnsWithConsecutiveMerge({
messages: withMergedAssistant,
role: "user",
merge: mergeConsecutiveUserTurns,
});
}
Why this is safe: mergeConsecutiveAssistantTurns is already exercised by the Gemini path on every request. Merging [thinking, text] + [text] produces [thinking, text, text], which is valid Anthropic content. This provides a structural guarantee: even if a future bug allows a mis-tagged echo to reach the validation layer, it will be silently resolved rather than surfaced as an API error. Risk: LOW.
Fix 3 — OPTIONAL: Persist recovery state at the session level
Location: wrapAnthropicStreamWithRecovery
When recovery fires, write a custom marker entry to the session:
await session.appendCustomEntry({
type: "anthropic-thinking-recovery",
firedAt: Date.now(),
strippedUpToMessageIndex: messages.length,
});
On the next turn, before building the API request, check for this marker and apply stripAllThinkingBlocks proactively to avoid the N-retry cascade.
Why this is lower priority: If Fix 1 and Fix 2 are applied, this error path should never be reached in the echo scenario. This fix targets the edge case where recovery is needed for reasons other than mis-tagged echoes (e.g., a genuine thinking block persisted across an unclean shutdown). Risk: MEDIUM (requires session state schema change).
Environment
| Field |
Value |
| OpenClaw version |
2026.5.26 |
| Model |
anthropic/claude-sonnet-4-6 |
| Channel |
webchat (TUI) |
| Thinking level |
medium |
| Error first observed |
2026-05-27T13:49 UTC |
| Error duration |
~23 minutes |
| Failed attempts before self-resolution |
8 |
| Session ID (for log correlation) |
38ff0e59 |
Additional Context
The 2026.5.26 release improved THINKING_BLOCK_ERROR_PATTERN to catch a related error (Invalid signature in thinking block). That fix correctly targets the thinking recovery logic. This bug is distinct: it lives in the session transcript writer, not the recovery path. The two bugs are orthogonal and can both be present simultaneously — the signature error triggers recovery, and the echo-tagging bug causes recovery to fire on every subsequent turn.
A quick test to confirm the fix for RC-1 is working: after applying it, inspect a new session file where subagent echo messages have been written and verify the entries show "provider": "openclaw" and "model": "delivery-mirror". If isTranscriptOnlyOpenClawAssistantMessage is evaluated on those entries, it should return true.
Investigated and reported after live observation during a 23-minute error window on 2026-05-27. Root causes confirmed by direct session file inspection and source analysis of the bundled JS. All proposed fixes reference existing production code — no new infrastructure required for Fix 1 or Fix 2.
Issue Title
Bug: Subagent announce-delivery echo messages inherit wrong provider/model metadata, causing persistent "thinking blocks cannot be modified" errors after gateway restart
GitHub Issue
Bug: Echo assistant messages from subagent announce-delivery bypass
isTranscriptOnlyOpenClawAssistantMessagefilter, causing persistent Anthropic API errors after gateway restartSummary
When a gateway restart occurs while a session has active thinking blocks, the subagent announce-delivery mechanism writes echo assistant messages to the parent session that inherit the original API response metadata (
provider="anthropic",model="claude-sonnet-4-6"). Because these messages are not tagged as internal echoes (provider="openclaw",model="delivery-mirror"), they bypass the existingisTranscriptOnlyOpenClawAssistantMessagefilter and appear as real consecutive assistant turns in subsequent API requests. This causes Anthropic to reject every subsequent user message with"thinking or redacted_thinking blocks in the latest assistant message cannot be modified"— an error that repeats on every turn for up to 23 minutes until the offending thinking block naturally rotates out of the message window.Steps to Reproduce
/thinking mediumExpected Behavior
Subagent announce-delivery echo messages should be transparently filtered from the API message replay. The session transcript should include them as internal bookkeeping entries only, invisible to the Anthropic API, with no impact on the conversation flow. Users should be able to send messages immediately after a gateway restart without error.
Actual Behavior
Every user message after the gateway restart triggers the Anthropic
"thinking or redacted_thinking blocks cannot be modified"error. The error self-resolves only once the offending thinking block rotates out of the active message context window (~23 minutes, 8 failed attempts observed in a single session).Root Cause Analysis
The failure is a chain of three distinct issues. RC-1 is the root cause; RC-2 and RC-3 amplify the damage.
RC-1 (Root of Roots): Echo messages are tagged with the wrong provider/model
When the announce-delivery path writes an echo of the assistant response to the parent session, it copies the original API response metadata verbatim. The resulting session entries look like this (from live session
38ff0e59):{"id": "8a80757e", "message": {"role": "assistant", "provider": "anthropic", "model": "claude-sonnet-4-6", "api": "anthropic-messages", "content": [{"type": "text", "text": "..."}]}} {"id": "7e1f9a8e", "message": {"role": "assistant", "provider": "anthropic", "model": "claude-sonnet-4-6", "api": "anthropic-messages", "content": [{"type": "text", "text": "..."}]}}An existing filter (
isTranscriptOnlyOpenClawAssistantMessage) is specifically designed to identify internal echo messages and exclude them from API replay:Because the echo messages carry
provider="anthropic", this predicate returnsfalse. The echoes pass through as real assistant turns, creating consecutive assistant turns in the API payload — with the earlier turn containing athinkingblock that Anthropic refuses to allow in any position other than the final assistant message.RC-2 (Amplifier):
validateAnthropicTurnsdoes not merge consecutive assistant turnsThe Gemini validation path already handles consecutive assistant turns correctly:
The Anthropic path does not:
mergeConsecutiveAssistantTurnsexists and is battle-tested. It is simply not wired into the Anthropic path. If it were, a leaking echo that slips past RC-1 would be silently merged rather than triggering an API error.RC-3 (Amplifier): Recovery is per-request, not per-session
wrapAnthropicStreamWithRecoveryfires when it detects a thinking-block error, strips the thinking blocks, and retries. However,recoveredAnthropicThinkingis a per-request flag. The next user message creates a fresh request object withrecoveredAnthropicThinking = false. The same error fires, recovery fires again, and this cycle repeats N times until the original thinking block organically scrolls past positionmessages[25].This is not the proximate cause, but it is what turns a single structural bug into a 23-minute outage.
Proposed Fixes
Fix 1 — PRIMARY: Tag echo messages correctly at write time
Location: The announce-delivery message writer (wherever it appends the echo assistant message to the parent session transcript).
Change:
Why this is safe: The
delivery-mirrormodel value and theisTranscriptOnlyOpenClawAssistantMessagefilter are already present in production. This fix simply ensures the tag is applied at write time instead of relying on external consumers to detect echoes by other means. Risk: LOW.Fix 2 — DEFENSE IN DEPTH: Merge consecutive assistant turns in
validateAnthropicTurnsLocation:
src/agents/pi-embedded-helpers/turns.ts(or equivalent bundled source)Change:
Why this is safe:
mergeConsecutiveAssistantTurnsis already exercised by the Gemini path on every request. Merging[thinking, text]+[text]produces[thinking, text, text], which is valid Anthropic content. This provides a structural guarantee: even if a future bug allows a mis-tagged echo to reach the validation layer, it will be silently resolved rather than surfaced as an API error. Risk: LOW.Fix 3 — OPTIONAL: Persist recovery state at the session level
Location:
wrapAnthropicStreamWithRecoveryWhen recovery fires, write a
custommarker entry to the session:On the next turn, before building the API request, check for this marker and apply
stripAllThinkingBlocksproactively to avoid the N-retry cascade.Why this is lower priority: If Fix 1 and Fix 2 are applied, this error path should never be reached in the echo scenario. This fix targets the edge case where recovery is needed for reasons other than mis-tagged echoes (e.g., a genuine thinking block persisted across an unclean shutdown). Risk: MEDIUM (requires session state schema change).
Environment
anthropic/claude-sonnet-4-638ff0e59Additional Context
The 2026.5.26 release improved
THINKING_BLOCK_ERROR_PATTERNto catch a related error (Invalid signature in thinking block). That fix correctly targets the thinking recovery logic. This bug is distinct: it lives in the session transcript writer, not the recovery path. The two bugs are orthogonal and can both be present simultaneously — the signature error triggers recovery, and the echo-tagging bug causes recovery to fire on every subsequent turn.A quick test to confirm the fix for RC-1 is working: after applying it, inspect a new session file where subagent echo messages have been written and verify the entries show
"provider": "openclaw"and"model": "delivery-mirror". IfisTranscriptOnlyOpenClawAssistantMessageis evaluated on those entries, it should returntrue.Investigated and reported after live observation during a 23-minute error window on 2026-05-27. Root causes confirmed by direct session file inspection and source analysis of the bundled JS. All proposed fixes reference existing production code — no new infrastructure required for Fix 1 or Fix 2.