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
Opencode infinitely spams messages in a loop and responds to its' messages #49008
I added GLM-5.3-Flash via a custom provider (llmapi.ai). After sending a short test message, the model got stuck in an infinite reply loop, responding to its own text. It would have gone on for hours if I hadn't manually stopped it.
The key thing is what the loop actually does. I send one short message and the model doesn't repeat the same answer, it keeps talking to itself. I write "hi", it answers "hi, how are you?", then another assistant message says "I'm good, and you?", and it just continues that way until I interrupt it. Every message is new and coherent, it's like the assistant is playing both sides of a conversation.
45533 is not this one: there the model itself breaks and emits endless "!" in reasoning_content (the SGLang bug). Here the text is clean.
37855 is the closest, but there the same short reply gets regenerated over and over, and only in opencode web. Mine is a progressing conversation and it happens in the TUI.
26220 is a loop after tool calls, and I'm not calling any tools. 42871 is pure repeated identical requests burning quota, also not what I see.
My guess is opencode doesn't stop after finish_reason: stop and sends another request where the last message is the assistant's own reply, so the OpenAI-compatible endpoint just continues the assistant turn and the model answers itself. That looks like a loop/termination bug in opencode, not the provider or the model.
Setup: opencode 1.18.30, macOS 15.7.3, Ghostty (TUI), custom OpenAI-compatible provider on llmapi.ai, model GLM-5.3-Flash. I haven't tested opencode web yet.
I can post the opencode.json provider block and a debug log if it helps. The Streamable link expires on Sep 16, so I'll reupload the recording here.
Capture the raw request sequence and finish_reason around the second turn. If OpenCode sends its latest assistant message back without a new user or tool turn, that trace should show whether the loop is in the TUI or at the provider.
Got the trace. My guess was close, but the trigger is narrower, and it's on the provider side.
I proxied every request. The first one is normal: roles=[system, user]. After it returns, opencode sends the next one as roles=[system, user, assistant], with no new user message and the assistant's own reply sitting last. Every request after that just appends another assistant message, so the model never gets a fresh user turn and keeps continuing the conversation with itself.
The reason opencode keeps going is the finish_reason. Every response ends like this:
even when the response contains zero tool calls. Of the 9 responses I captured, only one actually had a tool call (an ls); the other 8 were plain text that still reported finish_reason: "tool_calls". opencode sees "tool_calls", expects tool calls to run, finds none, and re-invokes with the previous assistant message as the last message. The model then just answers itself.
That also explains why the symptom is a progressing self-dialogue rather than a repeated reply. Nothing is being regenerated, opencode is simply carrying the assistant turn forward.
So the loop is: provider returns a wrong finish_reason (api.llmapi.ai, model zai-org/GLM-5.3-Flash), and opencode trusts it. This is not #45533, nothing degenerates in reasoning_content. It looks like the same class as #37855 / #26220, just with a different trigger. A guard on "finish_reason says tool_calls but there are no tool calls" would probably stop it on opencode's side.
I can paste the full request/response sequence if it helps.
That trace points to an invalid finish_reason: the provider marks plain-text responses as tool_calls, so OpenCode re-enters the assistant turn without a tool result. The provider should fix the field, and OpenCode could stop when tool_calls is set but no calls are present.
Experiencing the same class of bug on v1.18.31, but with a different finish_reason value. Filing here instead of opening a duplicate because the underlying issue (opencode trusts provider's finish_reason and loops when it's wrong) is identical.
Setup
Provider: custom gateway proxying GLM-5.2, configured with npm: "@ai-sdk/anthropic"
Trigger: any user message (even just "你好")
What happens
The agent loop never terminates. First ESC aborts the current stream; the loop immediately restarts from step=0. Second ESC finally kills it.
Hard evidence (queried from opencode.db, session with ~360 assistant turns in one exchange):
Reporter's case: provider returns finish_reason: "tool_calls" for plain text → opencode expects tool calls, finds none, continues.
My case: provider returns finish_reason: "unknown" (or the Anthropic SDK fails to parse a missing/empty stop_reason) with zero output tokens → opencode doesn't see a terminal state and continues.
Both are "opencode re-invokes because finish_reason doesn't signal end-of-turn" — just with different invalid values.
Guard suggestion
The proposed fix for this issue (stop when tool_calls is set but no calls are present) should be broadened to also treat finish === "unknown"combined with tokens.output === 0 as a terminal state. In this case the provider produced no content, so continuing has no useful purpose — the loop can only ever burn more tokens with no output.
Tried and did not work
agent.sisyphus.steps: 1 — ignored, session still ran ~30 steps before first ESC.
provider.tal.options.chunkTimeout: 5000 — did not fire; SSE chunks keep arriving with empty content.
Disabling runtime-fallback / model-fallback / all todo-continuation-* / background-notification hooks via oh-my-openagent plugin — no effect. This is not a plugin continuation, it's the core agent loop.
unknown with zero output should break the loop, but OpenCode should show it as a provider/protocol error rather than a normal completed turn. That stops the runaway session without masking malformed or unparseable SSE responses.
Activity
github-actions commentedon Sep 14, 2026
This issue might be a duplicate of existing issues. Please check:
If your issue is distinct from those, please leave a comment explaining how it differs.
nobottomline commentedon Sep 14, 2026
This isn't a duplicate of the linked issues.
The key thing is what the loop actually does. I send one short message and the model doesn't repeat the same answer, it keeps talking to itself. I write "hi", it answers "hi, how are you?", then another assistant message says "I'm good, and you?", and it just continues that way until I interrupt it. Every message is new and coherent, it's like the assistant is playing both sides of a conversation.
45533 is not this one: there the model itself breaks and emits endless "!" in reasoning_content (the SGLang bug). Here the text is clean.
37855 is the closest, but there the same short reply gets regenerated over and over, and only in opencode web. Mine is a progressing conversation and it happens in the TUI.
26220 is a loop after tool calls, and I'm not calling any tools. 42871 is pure repeated identical requests burning quota, also not what I see.
My guess is opencode doesn't stop after finish_reason: stop and sends another request where the last message is the assistant's own reply, so the OpenAI-compatible endpoint just continues the assistant turn and the model answers itself. That looks like a loop/termination bug in opencode, not the provider or the model.
Setup: opencode 1.18.30, macOS 15.7.3, Ghostty (TUI), custom OpenAI-compatible provider on llmapi.ai, model GLM-5.3-Flash. I haven't tested opencode web yet.
I can post the opencode.json provider block and a debug log if it helps. The Streamable link expires on Sep 16, so I'll reupload the recording here.
Alex668866 commentedon Sep 14, 2026
Capture the raw request sequence and
finish_reasonaround the second turn. If OpenCode sends its latest assistant message back without a new user or tool turn, that trace should show whether the loop is in the TUI or at the provider.nobottomline commentedon Sep 14, 2026
Got the trace. My guess was close, but the trigger is narrower, and it's on the provider side.
I proxied every request. The first one is normal: roles=[system, user]. After it returns, opencode sends the next one as roles=[system, user, assistant], with no new user message and the assistant's own reply sitting last. Every request after that just appends another assistant message, so the model never gets a fresh user turn and keeps continuing the conversation with itself.
The reason opencode keeps going is the finish_reason. Every response ends like this:
data: {"choices":[{"delta":{"content":null},"finish_reason":"tool_calls","index":0}]}
even when the response contains zero tool calls. Of the 9 responses I captured, only one actually had a tool call (an
ls); the other 8 were plain text that still reportedfinish_reason: "tool_calls". opencode sees "tool_calls", expects tool calls to run, finds none, and re-invokes with the previous assistant message as the last message. The model then just answers itself.That also explains why the symptom is a progressing self-dialogue rather than a repeated reply. Nothing is being regenerated, opencode is simply carrying the assistant turn forward.
So the loop is: provider returns a wrong finish_reason (api.llmapi.ai, model zai-org/GLM-5.3-Flash), and opencode trusts it. This is not #45533, nothing degenerates in reasoning_content. It looks like the same class as #37855 / #26220, just with a different trigger. A guard on "finish_reason says tool_calls but there are no tool calls" would probably stop it on opencode's side.
I can paste the full request/response sequence if it helps.
Alex668866 commentedon Sep 14, 2026
That trace points to an invalid
finish_reason: the provider marks plain-text responses astool_calls, so OpenCode re-enters the assistant turn without a tool result. The provider should fix the field, and OpenCode could stop whentool_callsis set but no calls are present.amengmon commentedon Sep 17, 2026
Experiencing the same class of bug on v1.18.31, but with a different
finish_reasonvalue. Filing here instead of opening a duplicate because the underlying issue (opencode trusts provider'sfinish_reasonand loops when it's wrong) is identical.Setup
npm: "@ai-sdk/anthropic"What happens
The agent loop never terminates. First ESC aborts the current stream; the loop immediately restarts from
step=0. Second ESC finally kills it.Hard evidence (queried from
opencode.db, session with ~360 assistant turns in one exchange):finishtokens.output)unknowntool-calls""Sample assistant message record:
{ "role": "assistant", "agent": "Sisyphus - Ultraworker", "cost": 0, "tokens": {"input":0,"output":0,"reasoning":0,"cache":{"write":0,"read":0}}, "modelID": "glm-5.2", "providerID": "tal", "time": {"created": 1789631840516, "completed": 1789631844256}, "finish": "unknown" }How this variant differs from the reporter's case
finish_reason: "tool_calls"for plain text → opencode expects tool calls, finds none, continues.finish_reason: "unknown"(or the Anthropic SDK fails to parse a missing/emptystop_reason) with zero output tokens → opencode doesn't see a terminal state and continues.Both are "opencode re-invokes because
finish_reasondoesn't signal end-of-turn" — just with different invalid values.Guard suggestion
The proposed fix for this issue (stop when
tool_callsis set but no calls are present) should be broadened to also treatfinish === "unknown"combined withtokens.output === 0as a terminal state. In this case the provider produced no content, so continuing has no useful purpose — the loop can only ever burn more tokens with no output.Tried and did not work
agent.sisyphus.steps: 1— ignored, session still ran ~30 steps before first ESC.provider.tal.options.chunkTimeout: 5000— did not fire; SSE chunks keep arriving with empty content.runtime-fallback/model-fallback/ alltodo-continuation-*/background-notificationhooks viaoh-my-openagentplugin — no effect. This is not a plugin continuation, it's the core agent loop.Alex668866 commentedon Sep 17, 2026
unknownwith zero output should break the loop, but OpenCode should show it as a provider/protocol error rather than a normal completed turn. That stops the runaway session without masking malformed or unparseable SSE responses.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 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊