Repository navigation
Compaction permanently fails with 'Duplicate value for tool_call_id' after an interrupted tool run #40235
Description
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:
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)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
callIDbefore building model messages (drop theunknown/interrupted duplicate, keep thecompletedone), or - interrupted runs should not write a second tool part with an existing
callIDat 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(tablesmessage,part; parts of typetoolwith duplicatecallIDwhere one istool="unknown",error="Tool execution aborted"). - Workaround for the affected session: delete the duplicate
unknown/interrupted tool part (keep thecompletedone) while opencode is closed.
Activity
Nucletheus commentedon Aug 7, 2026
Same error class, different trigger: empty
tool_call_idfrom 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
invalidtool part withcallID: "":{"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
toolmessages withtool_call_id: ""; multiple identical empty ids get rejected: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
callIDis 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/missingcallIDbefore serializing model messages.Workaround confirmed: deleting the empty-
callIDparts frompartin opencode.db recovered the session immediately (opencode v1.18.15).github-actions commentedon Oct 8, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.
rekram1-node commentedon Oct 8, 2026
https://opencode.ai/v2/docs
this should be essentially impossible to hit in opencode v2