anomalyco / anomalyco/opencode
Gemini reuses tool call IDs (tool_0), colliding in the global job registry and hanging shell tool calls forever
@nexxeln is already working on this.
Since Aug 21, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
Foreground shell calls hang forever on Gemini models. The command actually runs and its output file is complete, but the tool part never settles, the timeout argument has no effect, and only an interrupt clears it.
It's an ID collision in the job registry:
packages/ai/src/protocols/gemini.ts:609assigns`tool_${nextToolCallId++}`and the counter resets per request (gemini.ts:661), so the first tool call of every Gemini request istool_0. Over 6 days of local history: 3032 Gemini tool calls, 434 distinct IDs,tool_0used 2570 times. Every other provider I've used (OpenAI, Anthropic, DeepSeek, GLM) was 100% unique, so they never hit this.packages/core/src/tool/plugin/shell.ts:295starts the job asid: context.id, the raw call ID.packages/core/src/job.ts:205-208: if an entry with that ID is stillrunning,start()returns the stale snapshot and never forks the newrun. Entries are never evicted.shell.ts:314then blocks on the dead job's deferreds (job.ts:278-281), which never resolve.
shell.create happens before job.start, which is why the command still executes and the output looks finished. The timeout argument settles the shell session that nothing is awaiting any more, so it does nothing. Interrupting calls job.cancel, which unpoisons that ID until the next leak, hence the stall/escape/stall cycle.
Some real examples that hung: ls -la .git with timeout: 5000 sat for 756s, git log -n 5 --oneline with timeout: 5000 for 182s. Hang rate for shell calls with an explicit timeout was 6% on Gemini vs 0.1% on a GPT model over 10x the volume.
Gemini does hand back a unique ID, it's already stored as providerState.functionCallId and just isn't used for the part/job ID. Using it would fix the immediate problem, though keying the registry on a provider-supplied string seems worth revisiting regardless.
Not #38291 or #42524, the process exits cleanly here.
OpenCode version
0.0.0-dev-17824 (dev)
Steps to reproduce
- Use a Gemini model.
- Get one shell job to leak, so a
tool_0entry staysrunningin the registry (tearing down the run scope without reachingjob.canceldoes it). - Any later Gemini shell call in the same server process is also
tool_0, and hangs forever.
At the job layer: start({id: "tool_0", run: <never settles>}), then start({id: "tool_0", run: <settles immediately>}) and block({id: "tool_0"}) blocks forever, second run never executes.
Operating System
Fedora Linux 44
Terminal
Alacritty
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.