Bug: Session corrupted when tool execution is aborted - missing tool_result causes API rejection #5486

Closed
opened 2026-02-16 17:53:15 -05:00 by yindo · 0 comments
Owner

Originally created by @popododo0720 on GitHub (Jan 13, 2026).

Originally assigned to: @rekram1-node on GitHub.

Summary

When a tool execution is aborted (e.g., user cancels during tool run), the session becomes permanently corrupted. Subsequent messages fail with Anthropic API error: tool_use ids were found without tool_result blocks.

Environment

  • OS: Linux
  • Provider: Anthropic (Claude)

Steps to Reproduce

  1. Start a conversation that triggers tool calls
  2. Abort/cancel during tool execution (e.g., press Ctrl+C or cancel button)
  3. Try to continue the conversation in the same session
  4. API returns error: messages.N: tool_use ids were found without tool_result blocks immediately after: toolu_XXXXX

Root Cause Analysis

When a tool is aborted, the tool call part is saved with status: "error" and error: "Tool execution aborted":

{
  "type": "tool",
  "callID": "toolu_019ArjTH5XqPy66uTLZ8KHpM",
  "tool": "edit",
  "state": {
    "status": "error",
    "input": {},
    "error": "Tool execution aborted",
    "time": { "start": 1768350546106, "end": 1768350546106 }
  }
}

However, no corresponding tool_result message is created. When the conversation is reconstructed for the next API call, it sends the tool_use block but not a matching tool_result, violating Anthropic's API contract.

Current Behavior in agent.go

In streamAndHandleEvents() (line 350-420), tool results are properly generated for:

  • Context cancellation (ctx.Done()) - lines 353-364
  • Permission denied - lines 396-410

But not for:

  • Tool execution errors/aborts that occur through other paths

Suggested Fix

In internal/llm/agent/agent.go, ensure ALL tool calls get a tool_result, even on error:

// After tool.Run() fails with any error (not just permission denied)
if toolErr != nil {
    toolResults[i] = message.ToolResult{
        ToolCallID: toolCall.ID,
        Content:    fmt.Sprintf("Tool execution failed: %v", toolErr),
        IsError:    true,
    }
    // Don't break - continue to generate results for remaining tools
}

Or in convertMessages() in anthropic.go, filter out incomplete tool calls when reconstructing messages.

Impact

  • Session becomes unusable after any tool abort
  • User must start a new session, losing conversation context
  • Particularly problematic for long sessions with valuable context

Workaround

Start a new session. The corrupted session cannot be recovered without manually editing the stored message files.


I'd like to work on this fix if that's okay. Happy to submit a PR.

Originally created by @popododo0720 on GitHub (Jan 13, 2026). Originally assigned to: @rekram1-node on GitHub. ## Summary When a tool execution is aborted (e.g., user cancels during tool run), the session becomes permanently corrupted. Subsequent messages fail with Anthropic API error: `tool_use ids were found without tool_result blocks`. ## Environment - OS: Linux - Provider: Anthropic (Claude) ## Steps to Reproduce 1. Start a conversation that triggers tool calls 2. Abort/cancel during tool execution (e.g., press Ctrl+C or cancel button) 3. Try to continue the conversation in the same session 4. API returns error: `messages.N: tool_use ids were found without tool_result blocks immediately after: toolu_XXXXX` ## Root Cause Analysis When a tool is aborted, the tool call part is saved with `status: "error"` and `error: "Tool execution aborted"`: ```json { "type": "tool", "callID": "toolu_019ArjTH5XqPy66uTLZ8KHpM", "tool": "edit", "state": { "status": "error", "input": {}, "error": "Tool execution aborted", "time": { "start": 1768350546106, "end": 1768350546106 } } } ``` However, **no corresponding `tool_result` message is created**. When the conversation is reconstructed for the next API call, it sends the `tool_use` block but not a matching `tool_result`, violating Anthropic's API contract. ## Current Behavior in `agent.go` In `streamAndHandleEvents()` (line 350-420), tool results are properly generated for: - ✅ Context cancellation (`ctx.Done()`) - lines 353-364 - ✅ Permission denied - lines 396-410 But **not** for: - ❌ Tool execution errors/aborts that occur through other paths ## Suggested Fix In `internal/llm/agent/agent.go`, ensure ALL tool calls get a `tool_result`, even on error: ```go // After tool.Run() fails with any error (not just permission denied) if toolErr != nil { toolResults[i] = message.ToolResult{ ToolCallID: toolCall.ID, Content: fmt.Sprintf("Tool execution failed: %v", toolErr), IsError: true, } // Don't break - continue to generate results for remaining tools } ``` Or in `convertMessages()` in `anthropic.go`, filter out incomplete tool calls when reconstructing messages. ## Impact - Session becomes unusable after any tool abort - User must start a new session, losing conversation context - Particularly problematic for long sessions with valuable context ## Workaround Start a new session. The corrupted session cannot be recovered without manually editing the stored message files. --- I'd like to work on this fix if that's okay. Happy to submit a PR.
yindo closed this issue 2026-02-16 17:53:15 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#5486