Skip to content

Web UI: external messageID can trigger extra/empty assistant turns (repro with and without proxy) #19284

Closed as not planned

Description

@vihrovart

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 stop turns) 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 messageID on POST /session/{sessionID}/message.
  • In controlled tests, supplying an external high-lexicographic messageID reproduces runaway/empty turns.
  • In normal terminal flow, this external messageID is 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) break

When 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 messageID values.

Plugins

No response

OpenCode version

1.2.24

Steps to reproduce

Reproduction (minimal)

  1. Create a new session.

  2. Send one message without messageID:

    • Expected and observed: 1 user turn -> 1 assistant turn.
  3. Send next message with external high lexicographic messageID (example: msg_ffffffffffffCUSTOMIDTESTXYZ):

    • Observed: multiple assistant turns are generated for one user turn, including empty stop turns.
  4. 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 text part
      • no tool part.

Screenshot and/or share link

No response

Operating System

Alpine Linux v3.23 (VERSION_ID=3.23.3)

Terminal

busybox

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 26, 2026
  2. github-actions commented on Mar 26, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    These 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.

  3. removed
    bugSomething isn't working
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  4. github-actions commented on Jul 5, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions