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
Agent stops after tool execution with OpenAI-compatible providers (Gemini, LiteLLM) #14972
When using OpenAI-compatible providers (e.g., Gemini 3 Flash via @ai-sdk/openai-compatible, LiteLLM), the agent loop stops after executing a tool call instead of continuing to process.
Root cause: These providers return finish_reason: "stop" even when the response contains tool calls. The OpenAI standard returns "tool_calls", but providers like Gemini and LiteLLM don't follow this convention.
In packages/opencode/src/session/prompt.ts, the loop exit condition only checks finish_reason:
if(lastAssistant?.finish&&!["tool-calls","unknown"].includes(lastAssistant.finish)&&lastUser.id<lastAssistant.id){break// Agent stops here even though tools were called}
Since finish_reason is "stop" (not "tool-calls"), the loop exits prematurely.
Steps to reproduce
Configure an OpenAI-compatible provider with Gemini (e.g., gemini-3-flash-preview) or LiteLLM
Send a prompt that triggers tool usage (e.g., "read file X")
The agent executes the tool but then stops instead of continuing to process the tool result
Expected behavior
After executing a tool call, the agent should continue processing (make another LLM call with the tool results) regardless of the provider's finish_reason.
Actual behavior
The agent stops after executing the tool. The user sees the tool output but the agent never processes it further.
If your issue is specifically about the finish_reason: "stop" root cause with OpenAI-compatible providers (as you've identified), it may be worth noting that in #14842 as additional context, or confirming whether they share the same underlying cause.
I did test provided PR with litellm router and openai gpt-5.2-codex, gemini-3-flash, gemini-3.1-pro. Initially, all models stopped conversations after any tool call response from LLM.
The PR fixed that, I did not affect other models that worked without it. Tested: anthropic sonnet-4.6 and opus-4.6 with bedrock provider, opensource models kimi, glm, qwen, and minimax from openrouter.
All request made through litellm router
Vote to merge PR ASAP
Reacted by gaowanqiu, vhautot, Karim Lalani, Arthur Brito Medeiros, Valentin Vivaldi, Paul, omavi, Eduardo de Vera Toquero, Raimund, Alexander Minyaev and 1 more
I've been keeping the PR branch (#14973) updated with the latest dev — the bug is still present upstream as of today.
On our end, we need this fix since our team uses a LiteLLM proxy to route requests to Gemini, OpenAI, and other providers. Without it, the agent stops after every tool call, which makes it essentially unusable for agentic workflows.
Great to see this is also helping folks running local LLMs — glad it's not just us!
Hoping a maintainer gets a chance to review this at some point. The fix is minimal (7 lines) and all CI checks pass.
Reacted by Paul, omavi, giovanni-zantedeschi-venko, Josh Klar, letmeseek, bcmc, Laurent and Jeroen van den Hoek
I also think this is the case. Using the opencode free model is normal. Using the OpenAI model, it works fine in the "plan" mode, but there will be issues in the "build" mode.
I checked preserved local session history in ~/.local/share/opencode/opencode.db and found a concrete case that matches this issue in normal github-copilot / gemini-3-flash-preview usage.
Observed across sessions from OpenCode 1.2.27, 1.3.0, 1.3.3, and 1.3.4.
Representative stored example:
session: ses_2c985a0feffeXDeX2g4mxRvUnm
title: Adjust map zoom levels and unify map style
assistant message: msg_d367a9f53001VlicFCVcETMmIC
That assistant message has these parts in order:
step-start
tool with tool = "task" and state.status = "completed"
So this is not just a UI impression: OpenCode recorded a real completed tool part, then still treated the turn as finished. That lines up exactly with the loop-exit bug described here and with the proposed fix in #14973 to check for tool parts before breaking on finish_reason = "stop".
I also found a separate Copilot Gemini issue where tool intent is emitted as plain text instead of structured tool parts; I'm adding that evidence to #15315 separately since it seems distinct.
Confirming this on v1.3.13 via a self-hosted LiteLLM proxy, and can add two additional failure modes beyond the Gemini hang.
1. Gemini (gemini-2.5-flash-preview) — same root cause as described
Exact behavior from the issue: agent executes the tool call, LiteLLM logs show the response arriving cleanly, then nothing. The proxy's hanging request checker fires every ~2.5 minutes indefinitely. No second dispatch ever happens. The finish_reason: "stop" explanation fits perfectly.
2. Qwen (via OpenRouter) — loop instead of hang
Rather than stopping, the agent enters a dispatch loop. LiteLLM logs show the same cache key hashed and dispatched twice within the same session after 1–2 tool calls, then the hanging checker fires indefinitely — neither stopping nor progressing. This may be the same finish_reason misread causing the loop-continuation logic to re-enter instead of break, depending on how the provider varies its response.
3. Qwen3-Coder (via NVIDIA) — infinite streaming loop, no stop token
A harder failure: the model never emits a stop token at all. streaming_handler.py streamed the same ~5 bullet points on repeat for 60+ seconds until a forced container restart. After restart, the new container continued receiving the same in-flight stream before the connection dropped. Likely a related but distinct issue — the model may be looping on tool-result processing at the generation level.
All three backends are disabled in config as a workaround.
Confirming this also reproduces with a third OpenAI-compatible provider, so it doesn't seem specific to Gemini/LiteLLM.
Provider: MTPLX (local inference server, OpenAI-compatible endpoint at http://127.0.0.1:8001/v1)
Model: gemma4-mtplx-optimized-speed
opencode version: 1.18.9
Reproduces in: both the plain TUI and via Xcode's ACP integration (opencode acp), so it's not client-specific.
Same pattern as described in the issue — a tool call executes successfully (confirmed via git diff, real correct edits land on disk), but the session ends with no final assistant message. Log shows the loop advancing and exiting immediately after the tool round, with no second stream call and no error:
This matches the root cause described above — the loop-exit check only looks at finish_reason against ["tool-calls", "unknown"], so any provider that returns "stop" on a turn that actually contained tool calls will silently truncate the session. Given it's now confirmed across Gemini, LiteLLM, and MTPLX, this seems like a fairly broad compatibility gap rather than a single-provider quirk — might be worth having the loop check for the presence of tool calls in the response directly, rather than trusting the provider's finish_reason string.
Activity
github-actions commentedon Feb 25, 2026
This issue might be a duplicate of existing issues. Please check:
If your issue is specifically about the
finish_reason: "stop"root cause with OpenAI-compatible providers (as you've identified), it may be worth noting that in #14842 as additional context, or confirming whether they share the same underlying cause.fix(core): prevent agent loop from stopping after tool calls with Ope…
alexsmirnov commentedon Feb 28, 2026
I did test provided PR with litellm router and openai gpt-5.2-codex, gemini-3-flash, gemini-3.1-pro. Initially, all models stopped conversations after any tool call response from LLM.
The PR fixed that, I did not affect other models that worked without it. Tested: anthropic sonnet-4.6 and opus-4.6 with bedrock provider, opensource models kimi, glm, qwen, and minimax from openrouter.
All request made through litellm router
Vote to merge PR ASAP
lalanikarim commentedon Mar 3, 2026
This is a major blocker for using local LLMs. Please advise on timeline to address this. TIA
peaster commentedon Mar 6, 2026
Echoing the local LLM sentiment. Especially with the new Qwen 3.5 models that are otherwise excellent in OpenCode
omavi commentedon Mar 6, 2026
Prioritizing support for local models also feels like it should be core to the OpenCode ethos.
At least in situations like this where someone has a small and reasonable fix with big impact.
Nothing more open than running open source models on local compute which I expect to get more and more popular as the space develops.
peltax commentedon Mar 8, 2026
#14973 fixed Qwen3.5-9B toolcalls for me.
Before the model would output xml and halt like this (task was to edit a single html file):
Output
valenvivaldi commentedon Mar 13, 2026
Hey everyone, thanks for all the thumbs up! 👍
I've been keeping the PR branch (#14973) updated with the latest
dev— the bug is still present upstream as of today.On our end, we need this fix since our team uses a LiteLLM proxy to route requests to Gemini, OpenAI, and other providers. Without it, the agent stops after every tool call, which makes it essentially unusable for agentic workflows.
Great to see this is also helping folks running local LLMs — glad it's not just us!
Hoping a maintainer gets a chance to review this at some point. The fix is minimal (7 lines) and all CI checks pass.
whuanle commentedon Mar 15, 2026
I also think this is the case. Using the opencode free model is normal. Using the OpenAI model, it works fine in the "plan" mode, but there will be issues in the "build" mode.
thatdaveguy1 commentedon Mar 29, 2026
I checked preserved local session history in
~/.local/share/opencode/opencode.dband found a concrete case that matches this issue in normalgithub-copilot / gemini-3-flash-previewusage.Observed across sessions from OpenCode
1.2.27,1.3.0,1.3.3, and1.3.4.Representative stored example:
ses_2c985a0feffeXDeX2g4mxRvUnmAdjust map zoom levels and unify map stylemsg_d367a9f53001VlicFCVcETMmICThat assistant message has these parts in order:
step-starttoolwithtool = "task"andstate.status = "completed"step-finishwithreason = "stop"Part IDs from the DB:
prt_d367aab57001TqwXlN1A62yYEI->step-startprt_d367aab5a0017DHPw77DfL01N0-> completedtool(task)prt_d367ac890002O5QsgpPAL5wNKL->step-finish(reason=stop)So this is not just a UI impression: OpenCode recorded a real completed tool part, then still treated the turn as finished. That lines up exactly with the loop-exit bug described here and with the proposed fix in
#14973to check for tool parts before breaking onfinish_reason = "stop".I also found a separate Copilot Gemini issue where tool intent is emitted as plain text instead of structured tool parts; I'm adding that evidence to
#15315separately since it seems distinct.valenvivaldi commentedon Mar 30, 2026
Branch updated — merged latest
devand resolved a conflict inpackages/opencode/src/session/prompt.ts.The fix in the PR is still necessary after the merge. Details on the conflict resolution and why here: #14973 (comment)
ctz23 commentedon Apr 1, 2026
Confirming this on v1.3.13 via a self-hosted LiteLLM proxy, and can add two additional failure modes beyond the Gemini hang.
1. Gemini (gemini-2.5-flash-preview) — same root cause as described
Exact behavior from the issue: agent executes the tool call, LiteLLM logs show the response arriving cleanly, then nothing. The proxy's hanging request checker fires every ~2.5 minutes indefinitely. No second dispatch ever happens. The
finish_reason: "stop"explanation fits perfectly.2. Qwen (via OpenRouter) — loop instead of hang
Rather than stopping, the agent enters a dispatch loop. LiteLLM logs show the same cache key hashed and dispatched twice within the same session after 1–2 tool calls, then the hanging checker fires indefinitely — neither stopping nor progressing. This may be the same
finish_reasonmisread causing the loop-continuation logic to re-enter instead of break, depending on how the provider varies its response.3. Qwen3-Coder (via NVIDIA) — infinite streaming loop, no stop token
A harder failure: the model never emits a stop token at all.
streaming_handler.pystreamed the same ~5 bullet points on repeat for 60+ seconds until a forced container restart. After restart, the new container continued receiving the same in-flight stream before the connection dropped. Likely a related but distinct issue — the model may be looping on tool-result processing at the generation level.All three backends are disabled in config as a workaround.
motclem commentedon Jul 29, 2026
Confirming this also reproduces with a third OpenAI-compatible provider, so it doesn't seem specific to Gemini/LiteLLM.
Provider: MTPLX (local inference server, OpenAI-compatible endpoint at http://127.0.0.1:8001/v1)
Model: gemma4-mtplx-optimized-speed
opencode version: 1.18.9
Reproduces in: both the plain TUI and via Xcode's ACP integration (opencode acp), so it's not client-specific.
Same pattern as described in the issue — a tool call executes successfully (confirmed via git diff, real correct edits land on disk), but the session ends with no final assistant message. Log shows the loop advancing and exiting immediately after the tool round, with no second stream call and no error:
timestamp=...T18:57:30.836Z level=INFO message=stream providerID=mtplx modelID=gemma4-mtplx-optimized-speed ... agent=build mode=primary
timestamp=...T18:58:18.550Z level=INFO message=tracking hash=... (file change tracked)
timestamp=...T18:58:18.570Z level=INFO message=loop session.id=... step=2
timestamp=...T18:58:18.570Z level=INFO message="exiting loop" session.id=...
This matches the root cause described above — the loop-exit check only looks at finish_reason against ["tool-calls", "unknown"], so any provider that returns "stop" on a turn that actually contained tool calls will silently truncate the session. Given it's now confirmed across Gemini, LiteLLM, and MTPLX, this seems like a fairly broad compatibility gap rather than a single-provider quirk — might be worth having the loop check for the presence of tool calls in the response directly, rather than trusting the provider's finish_reason string.