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
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.
Summary
An aborted/interrupted tool run leaves a second tool part with the same
callIDin the message. The next auto-compaction then re-sends the full history to the provider and fails with400 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: acompletedone (tool=todowrite) and anerrorone (tool=unknown,error: "Tool execution aborted",metadata.interrupted: true).Message
msg_fb9813f84...(sessionses_0507e87b...):prt_fb9814a85...:{"type":"tool","tool":"todowrite","callID":"functions.todowrite:1","state":{"status":"completed",...}}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):prt_fc35e5d6d...:{"type":"tool","tool":"todowrite","callID":"functions.todowrite:0","state":{"status":"completed",...}}prt_fc35e7502...:{"type":"tool","tool":"unknown","callID":"functions.todowrite:0","state":{"status":"error","input":{},"raw":"","error":"Tool execution aborted","metadata":{"interrupted":true},...}}The
unknownpart withTool execution abortedis written when a run is interrupted; it duplicates thecallIDof the tool call that was already recorded ascompleted.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:
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.autoandpruneare enabled.Expected behavior
callIDbefore building model messages (drop theunknown/interrupted duplicate, keep thecompletedone), orcallIDat 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
~/.local/share/opencode/opencode.db(tablesmessage,part; parts of typetoolwith duplicatecallIDwhere one istool="unknown",error="Tool execution aborted").unknown/interrupted tool part (keep thecompletedone) while opencode is closed.