Skip to content

Compaction permanently fails with 'Duplicate value for tool_call_id' after an interrupted tool run #40235

Description

@diegomcolucci

Summary

An aborted/interrupted tool run leaves a second tool part with the same callID in the message. The next auto-compaction then re-sends the full history to the provider and fails with 400 invalid_request_error: Duplicate value for 'tool_call_id', so compaction never completes (summary never written, token counter never resets). The session is effectively poisoned — every subsequent compaction fails the same way.

Root cause evidence (local sqlite DB, opencode v1.18.11)

One assistant message contains two tool parts sharing the same callID: a completed one (tool=todowrite) and an error one (tool=unknown, error: "Tool execution aborted", metadata.interrupted: true).

Message msg_fb9813f84... (session ses_0507e87b...):

  • part prt_fb9814a85...: {"type":"tool","tool":"todowrite","callID":"functions.todowrite:1","state":{"status":"completed",...}}
  • part prt_fb9816675...: {"type":"tool","tool":"unknown","callID":"functions.todowrite:1","state":{"status":"error","input":{},"raw":"","error":"Tool execution aborted","metadata":{"interrupted":true},...}}

Message msg_fc35e22d5... (same session):

  • part prt_fc35e5d6d...: {"type":"tool","tool":"todowrite","callID":"functions.todowrite:0","state":{"status":"completed",...}}
  • part prt_fc35e7502...: {"type":"tool","tool":"unknown","callID":"functions.todowrite:0","state":{"status":"error","input":{},"raw":"","error":"Tool execution aborted","metadata":{"interrupted":true},...}}

The unknown part with Tool execution aborted is written when a run is interrupted; it duplicates the callID of the tool call that was already recorded as completed.

Failure symptom

Two consecutive auto-compactions in the same session failed identically:

  1. msg_fb9e6c8bf... (compaction, finish=None, err=true):
    Error from provider (DeepSeek): Duplicate value for 'tool_call_id' of functions.todowrite:1 in message[53] (400)
  2. msg_fc45786f2... (compaction, finish=None, err=true):
    Error from provider (Console): Upstream request failed: [invalid_request_error] Duplicate value for 'tool_call_id' of functions.todowrite:0 in message[224] (400)

Log excerpt:

timestamp=...T21:18:22.176Z level=ERROR ... agent=compaction mode=primary
  error.error="AI_APICallError: Error from provider (Console): Upstream request failed: [invalid_request_error] Duplicate value for 'tool_call_id' of functions.todowrite:0 in message[224]"

Because compaction serializes the entire history head to the model, the duplicate is always present → compaction aborts before producing a summary → the message count / token counter keep growing and the session never recovers, even though compaction.auto and prune are enabled.

Expected behavior

  • Tool parts must be deduplicated by callID before building model messages (drop the unknown/interrupted duplicate, keep the completed one), or
  • interrupted runs should not write a second tool part with an existing callID at all.

Impact

Sessions that suffer an interrupted tool run become permanently un-compactable (until a fresh session). Auto-compaction then loops on the same 400, the token counter stays high, and the user is forced to /new.

Repro notes / workaround

  • Took the evidence from ~/.local/share/opencode/opencode.db (tables message, part; parts of type tool with duplicate callID where one is tool="unknown", error="Tool execution aborted").
  • Workaround for the affected session: delete the duplicate unknown/interrupted tool part (keep the completed one) while opencode is closed.

Activity

  1. Nucletheus commented on Aug 7, 2026

    @Nucletheus

    Same error class, different trigger: empty tool_call_id from the big-pickle model (opencode Console provider).

    The model intermittently emits tool calls with an empty name and empty id. opencode records each as an invalid tool part with callID: "":

    {"type":"tool","tool":"invalid","callID":"","state":{"status":"completed",
     "input":{"tool":"","error":"Model tried to call unavailable tool ''. Available tools: bash, edit, glob, grep, invalid, ..."}}}

    One beira session accumulated 7 of these (plus 1 each in two other sessions). Every subsequent request replays them as tool messages with tool_call_id: ""; multiple identical empty ids get rejected:

    Error from provider (Console): Upstream request failed: [invalid_request_error] Duplicate value for 'tool_call_id' of  in message[101]
    

    The poisoned history also breaks model switching in the same session — minimax/m3 failed with Tool message must have tool_call_id (log line 160269).

    So the defect is broader than the interrupted-run case in the summary: tool parts whose callID is empty (or duplicated) are persisted and then re-sent as-is. Fix should dedupe AND assign a unique id (or drop) tool parts with empty/missing callID before serializing model messages.

    Workaround confirmed: deleting the empty-callID parts from part in opencode.db recovered the session immediately (opencode v1.18.15).

  2. github-actions commented on Oct 8, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

  3. rekram1-node commented on Oct 8, 2026

    @rekram1-node
    Collaborator

    https://opencode.ai/v2/docs

    this should be essentially impossible to hit in opencode v2

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions