Skip to content

With long running copilot sessions I've been hitting an issue: unexpected tool_use_id found in tool_result #1005

Description

@PureWeen

Describe the bug

✗ Model call failed: messages.0.content.1: unexpected tool_use_id found in tool_result blocks: toolu_vrtx_01SDJuRhCZGEMZ61MCTUXRM7. Each tool_result block must
have a corresponding tool_use block in the previous message. (Request ID: B218:798AF:2DF53B:341624:696A65C1)

Affected version

0.0.384

Steps to reproduce the behavior

I'm not sure, usually this happens if I've had a copilot cli session running for a couple days working on a problem.

I have a "/share" file that I can send to someone

alias: shneuvil on teams

Expected behavior

No response

Additional context

windows
powershell
x64

Activity

  1. added theissue type on Jan 16, 2026
  2. JohanVanosmael commented on Jan 16, 2026

    @JohanVanosmael

    I also encountered this when using --resume on a session, and traced it through the codebase.

    Root Cause Analysis

    The bug is in the K8l function in the Copilot codebase (index.js). This function is called during session.resume and abort events to repair conversation history, but it only handles one direction of orphaned messages.

    What K8l Currently Does

    function K8l(t) {
      if (t.length === 0) return t;
      
      let e = [];        // orphanedToolCallIds
      let n = new Set;   // seenToolResultIds  
      let r = false;     // foundAssistant
      
      // Iterates BACKWARDS through messages
      for (let o = t.length - 1; o >= 0; o--) {
        let s = t[o];
        if (s.role === "assistant" && (r = true), 
            s.role === "assistant" && "tool_calls" in s && s.tool_calls?.length > 0) {
          for (let I of s.tool_calls) n.has(I.id) || e.push(I.id);
        } else {
          if (r) break;  // ⚠️ STOPS scanning after first assistant message
          s.role === "tool" && s.tool_call_id && n.add(s.tool_call_id);
        }
      }
      
      if (e.length === 0) return t;
      
      // Adds fake "interrupted" results for orphaned tool_calls
      let l = "The execution of this tool, or a previous tool was interrupted.";
      let a = e.map(o => ({ role: "tool", tool_call_id: o, content: l }));
      return [...t, ...a];
    }

    The Bug

    Scenario Handled?
    Orphaned tool_calls (assistant has tool requests but no results) ✅ Adds fake "interrupted" results
    Orphaned tool_results (tool results exist but matching tool_call is missing) ❌ Not handled - causes API error

    Why This Causes the Error

    1. API Contract: Both Anthropic and OpenAI APIs require strict pairing - every tool_result must have a corresponding tool_use/tool_call in the previous assistant message

    2. Session Interruption: When a session is interrupted mid-tool-execution:

      • tool.execution_complete events may persist to disk
      • assistant.message events (containing toolRequests) may be lost (not yet flushed)
    3. On Resume: K8l is called but doesn't filter out orphaned tool_results

    4. Result: Messages containing tool_result without matching tool_use get sent to the API → validation error

    Proposed Fix

    Extend K8l to also filter out orphaned tool_results:

    function K8l(messages) {
      if (messages.length === 0) return messages;
    
      // Step 1: Collect ALL tool_call IDs from ALL assistant messages
      const allToolCallIds = new Set();
      for (const msg of messages) {
        if (msg.role === "assistant" && msg.tool_calls) {
          for (const tc of msg.tool_calls) allToolCallIds.add(tc.id);
        }
      }
    
      // Step 2: Filter out orphaned tool_results (no matching tool_call)
      const cleanedMessages = messages.filter(msg => {
        if (msg.role === "tool" && msg.tool_call_id) {
          if (!allToolCallIds.has(msg.tool_call_id)) {
            // Optionally log: Fe.warn(`Removing orphaned tool_result: ${msg.tool_call_id}`);
            return false;
          }
        }
        return true;
      });
    
      // Step 3: Find orphaned tool_calls (calls without results)
      const seenResultIds = new Set(
        cleanedMessages.filter(m => m.role === "tool").map(m => m.tool_call_id)
      );
      const orphanedCallIds = [...allToolCallIds].filter(id => !seenResultIds.has(id));
    
      // Step 4: Add fake results for orphaned tool_calls (existing logic)
      if (orphanedCallIds.length === 0) return cleanedMessages;
      
      const fakeResults = orphanedCallIds.map(id => ({
        role: "tool",
        tool_call_id: id,
        content: "The execution of this tool, or a previous tool was interrupted."
      }));
    
      return [...cleanedMessages, ...fakeResults];
    }

    How to Locate the Function

    Search for the string literal "The execution of this tool, or a previous tool was interrupted." in the codebase.


    Analysis performed by tracing: loadSession → $Y.fromEvents → event processing → K8l called on session.resume event

  3. ssfdre38 commented on Jan 17, 2026

    @ssfdre38
  4. dereklegenzoff commented on Jan 18, 2026

    @dereklegenzoff
    Contributor

    Hi @PureWeen - there was a bug in the CLI where in some cases, background compaction could lead to the conversation history getting into a bad state. This was fixed in version 0.0.384 but sessions started in older versions can still be affected.

    If you want to recover your old session, you can do the following:

    1. Resume the session
    2. Run the /session command and open up the events file included in the output
    3. Navigate to the last compaction_completed event, reduce the preCompactionMessagesLength value by 1, and save the file
    4. Close the CLI and resume the session again

    I'll close this issue since a fix has been deployed but please let us know if you run into this again in any new sessions.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions