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
Gemini reuses tool call IDs (tool_0), colliding in the global job registry and hanging shell tool calls forever #43940
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
Use a Gemini model.
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).
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.
This is fixed by #44051 and #45985: V2 preserves Gemini's supplied function-call IDs and generates unique fallback IDs, while shell jobs use their own shell IDs instead of raw tool-call IDs. This removes the job-registry collision described here. If you still see this on v2, please open a new issue with the details.
Activity
major commentedon Aug 21, 2026
I'll work up a PR.
neriousy commentedon Oct 20, 2026
This is fixed by #44051 and #45985: V2 preserves Gemini's supplied function-call IDs and generates unique fallback IDs, while shell jobs use their own shell IDs instead of raw tool-call IDs. This removes the job-registry collision described here. If you still see this on v2, please open a new issue with the details.