openclaw - 💡(How to fix) Fix Bug: Subagent announce-delivery echo messages inherit wrong provider/model metadata, causing persistent "thinking blocks cannot be modified" errors after gateway restart [1 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

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.


Error Message

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. 5. Observe the error immediately: 6. Send another message. The error fires again. Repeat for every message for approximately 20–23 minutes. 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. 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). 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. 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]. 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. 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). | Error first observed | 2026-05-27T13:49 UTC | | Error duration | ~23 minutes | 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. 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.

Root Cause

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.


Fix Action

Fixed

Code Example

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.

---

{"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": "..."}]}}

---

// 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");
}

---

// pi-embedded-helpers-seljzB3q.js
function validateGeminiTurns(messages) {
    return validateTurnsWithConsecutiveMerge({
        messages,
        role: "assistant",              // merges consecutive assistant turns
        merge: mergeConsecutiveAssistantTurns,
    });
}

---

function validateAnthropicTurns(messages) {
    return validateTurnsWithConsecutiveMerge({
        messages: stripDanglingAnthropicToolUses(messages),
        role: "user",                   // only merges user turns — no assistant merge
        merge: mergeConsecutiveUserTurns,
    });
}

---

// 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

---

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,
    });
}

---

await session.appendCustomEntry({
    type: "anthropic-thinking-recovery",
    firedAt: Date.now(),
    strippedUpToMessageIndex: messages.length,
});
RAW_BUFFERClick to expand / collapse

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

  1. Enable thinking in a session: /thinking medium
  2. Spawn one or more subagents that will deliver results back to the parent session.
  3. While a turn is in progress (subagent running), restart the OpenClaw gateway.
  4. After the gateway comes back, send any user message.
  5. 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.
  1. 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

FieldValue
OpenClaw version2026.5.26
Modelanthropic/claude-sonnet-4-6
Channelwebchat (TUI)
Thinking levelmedium
Error first observed2026-05-27T13:49 UTC
Error duration~23 minutes
Failed attempts before self-resolution8
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.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix Bug: Subagent announce-delivery echo messages inherit wrong provider/model metadata, causing persistent "thinking blocks cannot be modified" errors after gateway restart [1 pull requests]