Skip to content

Fix: tool_use_id mismatch error causes infinite loop instead of recovery #44473

@corydkidd

Description

@corydkidd

Bug Description

When the Anthropic API rejects a request due to a tool_use/tool_result mismatch error:

LLM request rejected: messages.156.content.2: unexpected tool_use_id found in tool_result blocks: toolu_01YMCQha57tDp359ZestPfwU. Each tool_result block must have a corresponding tool_use block in the previous message.

The error is formatted and sent to the user as a reply, but no recovery is attempted. When the user responds, the same broken conversation history is sent again, causing the same error — resulting in an infinite loop of the same error message.

Root Cause

  1. formatAssistantErrorText() catches the error via the generic invalid_request_error regex
  2. Returns "LLM request rejected: <message>" as user-facing text
  3. No special handling exists to suggest /new or attempt transcript repair
  4. User replies → same broken history → same error → loop

The existing repairToolUseResultPairing() runs on context build but the mismatch persisted, suggesting an edge case in the repair logic or timing.

Proposed Fix

Add detection for tool_use/tool_result mismatch errors alongside the existing role ordering error handling:

1. In errors.js - formatAssistantErrorText() (after line ~255):

// Catch tool_use/tool_result mismatch errors (transcript corruption)
if (/tool_use_id|tool_result.*corresponding.*tool_use|unexpected.*tool.*block/i.test(raw)) {
    return ("Conversation history corruption detected. " +
        "Please use /new to start a fresh session.");
}

2. In errors.js - sanitizeUserFacingText() (after role ordering check):

Same pattern added for consistency.

3. In run.js - promptError handler (after role ordering check ~line 333):

// Handle tool_use/tool_result mismatch errors (transcript corruption)
if (/tool_use_id|tool_result.*corresponding.*tool_use|unexpected.*tool.*block/i.test(errorText)) {
    return {
        payloads: [
            {
                text: "Conversation history corruption detected. " +
                    "Please use /new to start a fresh session.",
                isError: true,
            },
        ],
        meta: {
            durationMs: Date.now() - started,
            agentMeta: {
                sessionId: sessionIdUsed,
                provider,
                model: model.id,
            },
            systemPromptReport: attempt.systemPromptReport,
            error: { kind: "tool_result_mismatch", message: errorText },
        },
    };
}

Impact

  • Before: User sees repeated raw API error, no way to escape except manually running /new
  • After: User sees actionable message on first occurrence, loop is broken

Additional Notes

A more robust fix would be to attempt automatic transcript repair when this error is detected (calling repairToolUseResultPairing() and retrying), but the user-facing message is a safe fallback that prevents the loop.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions