You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Invalid-tool fallback persists callID:"" for empty-id tool calls → every later request 400s, session permanently bricked #53499
When a model streams a tool call with an empty id, opencode's invalid-tool fallback
writes the part as tool:"invalid" but leaves callID:"". From then on the session is
dead: every request replays that part and the provider rejects the body before the model
runs — retrying, clicking continue, even switching models all fail identically:
400 messages[25]: tool messages must include a non-empty string tool_call_id
(Error from provider (Console): Upstream request failed)
500 Cannot resolve tool name for tool_call_id=''. Either 'tool_calls[].function.name'
in assistant message or 'name' in tool message must be provided.
There is no UI path to remove that one message, and the request builder doesn't sanitize
empty ids, so the session cannot self-heal.
The persisted part (opencode.db, tables part + mirrored event):
{"type":"tool","tool":"invalid","callID":"",
"state":{"status":"completed",
"input":{"tool":"","error":"Model tried to call unavailable tool ''. Available tools: bash, edit, ..."},
"output":"The arguments provided to the tool are invalid: ..."}}
Stream side (mimo-v2.6-flash): every tool-call-delta chunk arrived with id: "" and no
name — only argumentsDelta — and the model also re-emitted the same call as literal
text. Same signature as #36299 and #52700.
Expected:
Ingest: empty/missing id → synthesize call_<random> before persisting. The
missing-name side already routes to the invalid tool; the id just slips through.
Request build: skip/repair parts with an empty callID when assembling messages, so
sessions that are already poisoned self-heal instead of needing manual DB surgery.
Workaround that fixed it (backup first; ran live against WAL, no restart needed):
UPDATE part SET data=replace(data,'"callID":""','"callID":"call_<random>"')
WHERE json_extract(data,'$.type')='tool'AND data LIKE'%"callID":""%';
UPDATE event SET data=replace(data,'"callID":""','"callID":"call_<random>"')
WHERE data LIKE'%"callID":""%';
After the replace: 0 empty ids, PRAGMA integrity_check ok, session streamed again on
the first try.
Related: #36299 (open), #52700 (auto-closed unfixed; mimo evidence in comments), #40235 (same family — persisted bad tool part → permanent 400s).
Failed parallel tool call leaves dangling tool_call_id, causing 400 errors on all subsequent requests #44852: closest sibling — also "persisted history → 400 on every later request", but
a different defect: a missing tool-result message for a valid call id (dangling id),
not an empty tool_call_id on an existing result. The defense-in-depth fix suggested
there (synthesize placeholder tool messages at serialization time) would heal both,
as would the build-time sanitation suggested here.
Happy to consolidate under #44852 if you'd rather track all "poisoned history replay"
cases in one place — otherwise the empty-id path seems distinct enough to track separately.
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.
Activity
github-actions commentedon Oct 6, 2026
This issue might be a duplicate of or closely related to existing issues. Please check:
tool_call_idsimilarly causes HTTP 400 errors on every subsequent request.ASIT-KuHaku commentedon Oct 6, 2026
Checked all five — none is a duplicate of this one, so keeping it open:
there the turn aborts and nothing bad gets persisted. This issue is about what happens
after: the invalid-tool fallback successfully persists the malformed call as a part
with callID:"", and that poisons every future request for the session. The persistence
step (and the missing build-time sanitation) is the bug reported here.
a different defect: a missing tool-result message for a valid call id (dangling id),
not an empty tool_call_id on an existing result. The defense-in-depth fix suggested
there (synthesize placeholder tool messages at serialization time) would heal both,
as would the build-time sanitation suggested here.
interrupted run, not an empty one.
Happy to consolidate under #44852 if you'd rather track all "poisoned history replay"
cases in one place — otherwise the empty-id path seems distinct enough to track separately.
thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊