anomalyco / anomalyco/opencode

Gemini reuses tool call IDs (tool_0), colliding in the global job registry and hanging shell tool calls forever

Open
#43,940 1 comment 0 reactions 1 assignee View on GitHub

@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:609 assigns `tool_${nextToolCallId++}` and the counter resets per request (gemini.ts:661), so the first tool call of every Gemini request is tool_0. Over 6 days of local history: 3032 Gemini tool calls, 434 distinct IDs, tool_0 used 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:295 starts the job as id: context.id, the raw call ID.
  • packages/core/src/job.ts:205-208: if an entry with that ID is still running, start() returns the stale snapshot and never forks the new run. Entries are never evicted.
  • shell.ts:314 then 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

  1. Use a Gemini model.
  2. Get one shell job to leak, so a tool_0 entry stays running in the registry (tearing down the run scope without reaching job.cancel does it).
  3. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.