openclaw - ✅(Solved) Fix [Bug]: Compactor preserves orphaned tool_use blocks, causing Anthropic API 400 errors on model switch [1 pull requests, 4 comments, 2 participants]

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…
GitHub stats
openclaw/openclaw#50094Fetched 2026-04-08 00:59:12
View on GitHub
Comments
4
Participants
2
Timeline
11
Reactions
0
Author
Timeline (top)
mentioned ×3subscribed ×3labeled ×2commented ×1

Error Message

When switching between models (e.g., Grok → Anthropic), orphaned tool_use blocks from the previous model can persist in session history. Anthropic's API strictly validates that every tool_use has a matching tool_result immediately after, and rejects the entire request with a 400 error: When the session was compacted (172k → 12k tokens), the orphaned tool call was preserved in the compacted output, moving the error from messages.173 to messages.1. The compactor does not sanitize tool_use/tool_result pairing before or after compaction. 4. Send a message — Anthropic rejects with 400 error about orphaned tool_use 5. Trigger compaction — error persists but moves to messages.1

  • If orphaned, either inject a synthetic tool_result with an error message, or strip the orphaned tool_use block entirely
  1. Send a message — Anthropic rejects with 400 error about orphaned tool_use
  2. Trigger compaction — error persists but moves to messages.1 When switching between models (e.g., Grok → Anthropic), orphaned tool_use blocks from the previous model can persist in session history. Anthropic's API strictly validates that every tool_use has a matching tool_result immediately after, and rejects the entire request with a 400 error: When the session was compacted (172k → 12k tokens), the orphaned tool call was preserved in the compacted output, moving the error from messages.173 to messages.1. The compactor does not sanitize tool_use/tool_result pairing before or after compaction.

Root Cause

  1. A Grok model produced a tool_use block with ID call78843178 (OpenAI/Grok format: call_XXXXXXXX)
  2. The corresponding tool_result was either lost or never recorded in the session transcript
  3. When the model was switched to anthropic/claude-opus-4-6, the orphaned tool call was sent to Anthropic's API
  4. Anthropic rejected it at messages.173

Fix Action

Fix / Workaround

  • Severity: High — completely blocks the session from using Anthropic models (direct or via LiteLLM proxy)

  • Workaround: Switch to OpenRouter (which is more lenient about validation), wait for compaction, then switch back — but this is unreliable since compaction preserves the corruption

  • Severity: High — completely blocks the session from using Anthropic models (direct or via LiteLLM proxy)

  • Workaround: Switch to OpenRouter (which is more lenient about validation), wait for compaction, then switch back — but this is unreliable since compaction preserves the corruption

PR fix notes

PR #50142: fix: strip orphaned tool_use blocks before sending to Anthropic API

Description (problem / solution / changelog)

Summary

When switching models (e.g. Grok → Anthropic), orphaned tool_use blocks can persist in session history without matching tool_result blocks. Anthropic's API strictly validates this pairing and rejects requests with 400 errors:

messages.173: `tool_use` ids were found without `tool_result` blocks immediately after: call78843178.

Root Cause

In repairToolUseResultPairing, assistant messages with stopReason: "error" or "aborted" were passed through unchanged. This was correct in avoiding synthetic tool results (which cause "unexpected tool_use_id" errors), but incorrect in leaving orphaned toolCall blocks that Anthropic then rejects.

When Grok produces a tool_use block and fails mid-response (stopReason: "error"), no tool_result is recorded. When the model switches to Anthropic, the orphaned block is sent to the API and rejected with 400.

Changes

  • src/agents/session-transcript-repair.ts: In repairToolUseResultPairing, for errored/aborted assistant messages with tool call blocks and no matching tool_result in the following messages, strip the orphaned tool call blocks (replacing with a fallback text content block). If matching results exist, the message passes through unchanged.

  • src/agents/session-transcript-repair.test.ts: Added two new tests:

    • Cross-model scenario: errored assistant message (Grok-style ID call78843178) with no following tool result → tool call blocks stripped
    • Errored assistant message with matching tool result → preserved (no strip)

Testing

  • All 25 tests in session-transcript-repair.test.ts pass (23 existing + 2 new)
  • No new test failures introduced (58 pre-existing failures unchanged)
  • Ran full src/agents/ test suite

Fixes openclaw/openclaw#50094

Changed files

  • openclaw-2026-03-07.log (added, +129/-0)
  • src/agents/session-transcript-repair.test.ts (modified, +68/-1)
  • src/agents/session-transcript-repair.ts (modified, +54/-0)
  • src/infra/json-files.ts (modified, +37/-13)
  • src/slack/monitor/provider.ts (modified, +59/-2)

Code Example

messages.173: `tool_use` ids were found without `tool_result` blocks immediately after: call78843178.
Each `tool_use` block must have a corresponding `tool_result` block in the next message.

---

messages.173: `tool_use` ids were found without `tool_result` blocks immediately after: call78843178.
Each `tool_use` block must have a corresponding `tool_result` block in the next message.

---
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Summary

Bug Description

When switching between models (e.g., Grok → Anthropic), orphaned tool_use blocks from the previous model can persist in session history. Anthropic's API strictly validates that every tool_use has a matching tool_result immediately after, and rejects the entire request with a 400 error:

messages.173: `tool_use` ids were found without `tool_result` blocks immediately after: call78843178.
Each `tool_use` block must have a corresponding `tool_result` block in the next message.

Root Cause

  1. A Grok model produced a tool_use block with ID call78843178 (OpenAI/Grok format: call_XXXXXXXX)
  2. The corresponding tool_result was either lost or never recorded in the session transcript
  3. When the model was switched to anthropic/claude-opus-4-6, the orphaned tool call was sent to Anthropic's API
  4. Anthropic rejected it at messages.173

Compaction does not fix it

When the session was compacted (172k → 12k tokens), the orphaned tool call was preserved in the compacted output, moving the error from messages.173 to messages.1. The compactor does not sanitize tool_use/tool_result pairing before or after compaction.

Expected Behavior

The message builder (or compactor) should:

  1. Validate tool_use/tool_result pairing before sending to the LLM API — strip orphaned tool_use blocks that have no matching tool_result
  2. During compaction, ensure the compacted output does not contain orphaned tool calls
  3. Ideally, handle cross-model tool ID format differences gracefully (Grok uses call_XXXXXXXX, Anthropic uses toolu_bdrk_*)

Impact

  • Severity: High — completely blocks the session from using Anthropic models (direct or via LiteLLM proxy)
  • Workaround: Switch to OpenRouter (which is more lenient about validation), wait for compaction, then switch back — but this is unreliable since compaction preserves the corruption

Environment

  • OpenClaw version: 2026.3.13 (commit 61d171a)
  • Models involved: or-main/x-ai/grok-4 (source of orphaned tool call) → anthropic/claude-opus-4-6 (rejects it)
  • Session type: Main Telegram direct session

Reproduction Steps

  1. Run a session on a Grok model (via OpenRouter)
  2. Have Grok make tool calls (the call_XXXXXXXX format IDs)
  3. Switch the session model to anthropic/claude-opus-4-6 (direct)
  4. Send a message — Anthropic rejects with 400 error about orphaned tool_use
  5. Trigger compaction — error persists but moves to messages.1

Suggested Fix

In the message builder (buildMessages or equivalent), before sending to an Anthropic-family API:

  • Walk the message array and verify every tool_use content block has a matching tool_result in the next message
  • If orphaned, either inject a synthetic tool_result with an error message, or strip the orphaned tool_use block entirely
  • Apply the same sanitization in the compactor output

Steps to reproduce

  1. Run a session on a Grok model (via OpenRouter)
  2. Have Grok make tool calls (the call_XXXXXXXX format IDs)
  3. Switch the session model to anthropic/claude-opus-4-6 (direct)
  4. Send a message — Anthropic rejects with 400 error about orphaned tool_use
  5. Trigger compaction — error persists but moves to messages.1

Expected behavior

The message builder (or compactor) should:

  1. Validate tool_use/tool_result pairing before sending to the LLM API — strip orphaned tool_use blocks that have no matching tool_result
  2. During compaction, ensure the compacted output does not contain orphaned tool calls
  3. Ideally, handle cross-model tool ID format differences gracefully (Grok uses call_XXXXXXXX, Anthropic uses toolu_bdrk_*)

Actual behavior

Bug Description

When switching between models (e.g., Grok → Anthropic), orphaned tool_use blocks from the previous model can persist in session history. Anthropic's API strictly validates that every tool_use has a matching tool_result immediately after, and rejects the entire request with a 400 error:

messages.173: `tool_use` ids were found without `tool_result` blocks immediately after: call78843178.
Each `tool_use` block must have a corresponding `tool_result` block in the next message.

Root Cause

  1. A Grok model produced a tool_use block with ID call78843178 (OpenAI/Grok format: call_XXXXXXXX)
  2. The corresponding tool_result was either lost or never recorded in the session transcript
  3. When the model was switched to anthropic/claude-opus-4-6, the orphaned tool call was sent to Anthropic's API
  4. Anthropic rejected it at messages.173

Compaction does not fix it

When the session was compacted (172k → 12k tokens), the orphaned tool call was preserved in the compacted output, moving the error from messages.173 to messages.1. The compactor does not sanitize tool_use/tool_result pairing before or after compaction.

OpenClaw version

2026.03.13

Operating system

Ubuntu 24.04

Install method

npm global

Model

anthropic/claude-opus-4-6

Provider / routing chain

openclaw -> anthropic

Config file / key location

No response

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

Impact

  • Severity: High — completely blocks the session from using Anthropic models (direct or via LiteLLM proxy)
  • Workaround: Switch to OpenRouter (which is more lenient about validation), wait for compaction, then switch back — but this is unreliable since compaction preserves the corruption

Additional information

No response

extent analysis

Fix Plan

To resolve the issue of orphaned tool_use blocks, we need to modify the message builder to validate tool_use and tool_result pairings before sending to the LLM API. Here are the steps:

  1. Modify the message builder:

    • Iterate through the message array to check for tool_use blocks.
    • For each tool_use block, verify that it has a corresponding tool_result block immediately after it.
    • If an orphaned tool_use block is found, either inject a synthetic tool_result with an error message or remove the tool_use block entirely.
  2. Handle compaction:

    • Ensure that during compaction, the compacted output does not contain orphaned tool calls.
    • Apply the same validation and sanitization as in the message builder.
  3. Handle cross-model tool ID format differences:

    • Gracefully handle differences in tool ID formats between models (e.g., Grok's call_XXXXXXXX vs. Anthropic's toolu_bdrk_*).

Example Code (Python):

def sanitize_tool_use(message_array):
    sanitized_array = []
    for i, message in enumerate(message_array):
        if message['type'] == 'tool_use':
            # Check if there's a corresponding tool_result
            if i + 1 < len(message_array) and message_array[i + 1]['type'] == 'tool_result':
                sanitized_array.append(message)
                sanitized_array.append(message_array[i + 1])
            else:
                # Either inject a synthetic tool_result or skip this tool_use
                # For simplicity, let's inject a synthetic tool_result
                synthetic_tool_result = {
                    'type': 'tool_result',
                    'id': message['id'],
                    'error': 'Synthetic tool result for orphaned tool use'
                }
                sanitized_array.append(message)
                sanitized_array.append(synthetic_tool_result)
        else:
            sanitized_array.append(message)
    return sanitized_array

# Example usage
message_array = [
    {'type': 'tool_use', 'id': 'call_123456'},
    {'type': 'text', 'content': 'Hello'},
    {'type': 'tool_use', 'id': 'call_789012'}
]

sanitized_array = sanitize_tool_use(message_array)
print(sanitized_array)

Verification

To verify that the fix worked:

  • Run a session on a Grok model and make tool calls.
  • Switch the session model to Anthropic.
  • Send a message and check if Anthropic's API accepts it without rejecting due to orphaned tool_use blocks.
  • Trigger compaction and verify that the compacted output does not contain orphaned tool calls.

Extra Tips

  • Ensure that the solution is applied both in the message builder and during compaction to prevent orphaned

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…

FAQ

Expected behavior

The message builder (or compactor) should:

  1. Validate tool_use/tool_result pairing before sending to the LLM API — strip orphaned tool_use blocks that have no matching tool_result
  2. During compaction, ensure the compacted output does not contain orphaned tool calls
  3. Ideally, handle cross-model tool ID format differences gracefully (Grok uses call_XXXXXXXX, Anthropic uses toolu_bdrk_*)

Still need to ship something?

×6

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

Back to top recommendations

TRENDING