Repository navigation
Web UI: external messageID can trigger extra/empty assistant turns (repro with and without proxy) #19284
Description
Description
Summary
When POST /session/{sessionID}/message is called with an externally supplied messageID, OpenCode may create multiple assistant turns for a single user turn, including empty turns (step-start + step-finish, reason stop, no text/tool parts).
This is reproducible with different model providers (Gemini/OpenAI).
Impact
- In Web UI, user may see missing or unstable assistant rendering.
- Session gets extra assistant records with no visible text.
- In severe cases this can become a runaway loop (many assistant turns generated for one user turn).
Web UI vs Terminal
Observed behavior differs by client path:
- Web UI path (problematic): can trigger extra assistant turns (including empty
stopturns) for a single user turn. - Terminal path (stable in normal use): did not reproduce this symptom in regular usage.
Why this likely happens:
- OpenCode session API accepts optional external
messageIDonPOST /session/{sessionID}/message. - In controlled tests, supplying an external high-lexicographic
messageIDreproduces runaway/empty turns. - In normal terminal flow, this external
messageIDis typically not supplied by user interaction path, so the issue is not triggered.
Why this looks like loop control issue
In OpenCode session/prompt.ts, loop stop condition uses string comparison:
if (lastUser.id < lastAssistant.id) breakWhen an external user messageID is lexicographically greater than the generated assistant id, this condition fails and loop may continue unexpectedly.
Files of interest (OpenCode)
packages/opencode/src/session/prompt.ts(loop condition + prompt flow)packages/opencode/src/session/schema.ts(message ID schema)packages/opencode/src/id/id.ts(ID generation semantics)
Notes
- OpenAI terminal streaming chunk (
delta: {},finish_reason: "stop") is protocol-valid and should not be stripped. - The issue reproduces independently from a specific upstream provider, which suggests session loop/message-id handling rather than provider protocol incompatibility.
- Any integration can still trigger this if the client path provides problematic external
messageIDvalues.
Plugins
No response
OpenCode version
1.2.24
Steps to reproduce
Reproduction (minimal)
-
Create a new session.
-
Send one message without
messageID:- Expected and observed: 1 user turn -> 1 assistant turn.
-
Send next message with external high lexicographic
messageID(example:msg_ffffffffffffCUSTOMIDTESTXYZ):- Observed: multiple assistant turns are generated for one user turn, including empty
stopturns.
- Observed: multiple assistant turns are generated for one user turn, including empty
-
Compare with terminal usage (same backend/model route):
- Observed in normal terminal operation: no runaway empty-turn pattern.
- This supports a client-path trigger rather than an upstream provider protocol issue.
Control reproduction with direct provider
To eliminate integration-specific influence, run the same external messageID scenario with a direct OpenCode provider (e.g. openai).
Observed result:
- The issue still reproduces (extra assistant turn appears in the same user block).
- Therefore this issue can be triggered without any custom intermediary layer.
Example API sequence
# 1) create session
POST /session
# 2) normal prompt (no messageID)
POST /session/{sessionID}/message
{
"model": {"providerID": "openai", "modelID": "gpt-5.4"},
"parts": [{"type": "text", "text": "hello"}]
}
# 3) prompt with external messageID
POST /session/{sessionID}/message
{
"messageID": "msg_ffffffffffffCUSTOMIDTESTXYZ",
"model": {"providerID": "openai", "modelID": "gpt-5.4"},
"parts": [{"type": "text", "text": "hello again"}]
}Observed data pattern in DB/API
- For the problematic user block:
- first assistant turn has text,
- followed by one or more assistant turns with:
- parts:
step-start,step-finish step-finish.reason = "stop"- no
textpart - no
toolpart.
- parts:
Screenshot and/or share link
No response
Operating System
Alpine Linux v3.23 (VERSION_ID=3.23.3)
Terminal
busybox
Activity
github-actions commentedon Mar 26, 2026
This issue might be a duplicate of existing issues. Please check:
messageIDsorts lexicographically after server-generated assistant IDs, breaking thelastUser.id < lastAssistant.idexit condition inprompt.tsprompt.tsloop condition bug with external/client-generated IDsprompt.tswith ID string comparison failingThese issues all describe the same underlying bug in the session loop termination condition. Please review them to determine if this is a duplicate before proceeding.
github-actions commentedon Jul 5, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.