Skip to content

fix(ai): add repair for orphaned tool calls#9976

Merged
Light2Dark merged 2 commits into
mainfrom
sham/fix-anthropic-interrupted-tool-calls
Jun 25, 2026
Merged

fix(ai): add repair for orphaned tool calls#9976
Light2Dark merged 2 commits into
mainfrom
sham/fix-anthropic-interrupted-tool-calls

Conversation

@Light2Dark

@Light2Dark Light2Dark commented Jun 24, 2026

Copy link
Copy Markdown
Member

📝 Summary

image

Fixes an anthropic.BadRequestError that occurs when a user stops an AI chat mid–tool-call and then continues the conversation:

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

logfire trace: https://logfire-us.pydantic.dev/public-trace/b1d4c2e1-1b7d-4219-badb-fe96103941ad?spanId=6d7eaf8fabb7761f

Backend fix

  • Adds repair_incomplete_tool_call, which rewrites any tool part still in input-streaming / input-available into the matching typed output-error part (ToolOutputErrorPart / DynamicToolOutputErrorPart) with a "Tool call was interrupted and did not complete." message, so the conversion to pydantic-ai produces a valid tool_result.
  • Wires it into convert_to_pydantic_messages, running after UIMessage construction

Frontend UI change

  • The spinner used to continually spin on broken tool calls. This makes it cancelled.

📋 Pre-Review Checklist

  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • Video or media evidence is provided for any visual changes (optional).

✅ Merge Checklist

  • I have read the contributor guidelines.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Tests have been added for the changes made.

Review in cubic

@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jun 25, 2026 5:51am

Request Review

@Light2Dark Light2Dark added the bug Something isn't working label Jun 24, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 7 files

Architecture diagram
sequenceDiagram
    participant User as User (Browser)
    participant UI as Chat UI Components
    participant API as Backend API Route
    participant Repair as repair_incomplete_tool_call
    participant Convert as convert_to_pydantic_messages
    participant AI as AI Provider (Anthropic)

    Note over User,AI: Normal streaming flow (first request)

    User->>UI: Send message
    UI->>API: POST /chat (user message)
    API->>AI: Stream response
    AI-->>API: tool_use (tool call)
    API-->>UI: Stream tool part (input-streaming)
    UI->>UI: Show spinner for tool call
    User->>UI: Stop stream / abort
    UI->>API: Cancel request
    API->>AI: Abort stream
    Note over AI: tool_use left without<br/>corresponding tool_result

    Note over User,AI: Continue flow (second request) — NEW: repair
    
    User->>UI: Send "continue"
    UI->>API: POST /chat (user message + history)
    API->>Convert: Convert UIMessages to pydantic messages
    Convert->>Repair: NEW: repair_incomplete_tool_call(part)
    Note over Repair: Detect tool part in<br/>input-streaming / input-available
    alt Incomplete static tool call (ToolInputStreamingPart / ToolInputAvailablePart)
        Repair->>Repair: Rewrite to ToolOutputErrorPart
        Repair-->>Convert: ToolOutputErrorPart (with interrupted message)
    else Incomplete dynamic tool call (DynamicToolInputStreamingPart / DynamicToolInputAvailablePart)
        Repair->>Repair: Rewrite to DynamicToolOutputErrorPart
        Repair-->>Convert: DynamicToolOutputErrorPart (with interrupted message)
    else Terminal or non-tool part
        Repair-->>Convert: unchanged part
    end
    Convert-->>API: Repaired pydantic messages
    Note over Convert: Now every tool_use has a<br/>matching tool_result (error)
    API->>AI: Send repaired conversation
    AI-->>API: Accepts messages (no 400 error)
    API-->>UI: New response stream

    Note over UI: Frontend display — CHANGED

    UI->>UI: Render tool call with isActive=false
    UI->>UI: Show CircleSlashIcon + "Interrupted" label
    Note over UI: Previously spinner would loop forever<br/>now shows cancelled state
Loading

Re-trigger cubic

@Light2Dark Light2Dark changed the title add repair for orphaned tool calls Jun 24, 2026
@Light2Dark Light2Dark force-pushed the sham/fix-anthropic-interrupted-tool-calls branch from 1d95e74 to 5b51736 Compare June 24, 2026 16:13
@Light2Dark Light2Dark merged commit 972242e into main Jun 25, 2026
43 checks passed
@Light2Dark Light2Dark deleted the sham/fix-anthropic-interrupted-tool-calls branch June 25, 2026 16:21
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.11-dev28

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

2 participants