Skip to content

[Bug]: Assistant messages stay marked as streaming after the server restarts mid-response #14572

Closed

Description

@iamnotmili

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Start a turn whose provider streams a multi-paragraph response (default "paragraph" streaming mode).
  2. As soon as the first paragraph appears, stop the T3 server: Ctrl+C, an app update, or a crash. SIGTERM and SIGKILL behave the same.
  3. Start the server again and open the thread.

Expected behavior

Startup reconciliation already marks the orphaned session as failed ("Provider session did not survive a server restart. Send a new message to continue."). The partial assistant message it left behind should be completed too, keeping its text.

Actual behavior

The partial message keeps streaming: true (projection_thread_messages.is_streaming = 1). Its timestamp stays hidden and the Run buttons on its code blocks stay disabled. The flag never clears: it survives later turns that complete normally and further clean restarts.

Cause: ProviderRuntimeIngestion completes the messages it streams from in-memory state (finalizeAssistantMessage / finalizeActiveSegmentForTurn), and that state dies with the process. reconcileProviderSessions in apps/server/src/serverRuntimeStartup.ts only dispatches thread.session.set (status error, or starting when the thread continues after an update). No thread.message.assistant.complete or thread.message.reasoning.complete is sent for rows already projected as streaming. Reading the code, the continue-after-update path leaves the old message streaming too; I reproduced only the error path end to end.

Related: #14433 fixes the same symptom in orchestrator V2's ProviderRuntimeRecoveryService. This report is about the startup reconciliation that ships on main.

I have a focused fix with tests and can open a PR if this is triaged.

Impact

Minor bug or occasional failure

Version or commit

main @ 0cf482b (2026-09-30); the reconciliation code is unchanged on main as of 2026-10-01

Environment

Linux, Node 24.21.0, server run from source. Reproduced with the repo's ACP mock agent (scripts/acp-mock-agent.ts in content-then-hang mode, with the streamed text containing a paragraph break) behind the Cursor provider. Reconciliation does not depend on the provider.

Logs or stack traces

before stop:   is_streaming=1  text="First paragraph of the answer.\n\n"
after restart: is_streaming=1  (unchanged; still 1 after the next turn and after another clean restart)

Screenshots, recordings, or supporting files

No response

Workaround

None. Sending a new message does not clear the old flag.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 1, 2026
  2. juliusmarminge commented on Oct 1, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks for the clear repro and diagnosis. I confirmed this is a real bug on main, and #14433 (open PR) doesn't cover it.

    Startup reconciliation already settles the orphaned provider session: reconcileProviderSessions in apps/server/src/serverRuntimeStartup.ts only dispatches thread.session.set (error with "Provider session did not survive a server restart. Send a new message to continue.", or starting on the continue-after-update path). thread.session-set doesn't touch messages, and the command read model used there always has messages: [], so the reconciler never even sees the streaming rows.

    The streaming flag gets set by thread.message.assistant.delta / thread.message.reasoning.delta and is only cleared by the matching *.complete command. Those completes come from ProviderRuntimeIngestion (finalizeAssistantMessage / finalizeActiveSegmentForTurn), and its segment state and text buffer live in process memory, so they die with the server. Paragraph mode makes this easy to hit: a finished paragraph is sent as a delta (is_streaming = 1, text kept), and it isn't completed until the segment finalizes. After a restart the row stays streaming. A later turn uses a new message id, and once the session is error with no active turn, later restarts never look at it again. The client hides the timestamp, the copy control, and code-block Run while message.streaming is set, which matches what you saw.

    #14433 clears the same flag in Orchestrator V2's ProviderRuntimeRecoveryService on t3code/codex-turn-mapping, but it doesn't change this startup path.

    What should happen: when an orphaned session is reconciled, any assistant and reasoning rows for that turn that are already projected as streaming should be completed, keeping their projected text. *.complete already does that, because an empty complete text leaves the existing text in place. This should cover both the error settle and the continue-after-update path. Text that never left the in-memory buffer is gone with the process, so there's no need to try to rebuild it.

    You mentioned you have a fix with tests. A focused PR that links this issue and adds coverage for the flag in the existing reconcile tests is very welcome.

  3. added
    via-triageFiled through npx t3 triage
    and removed
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 1, 2026
  4. t3dotgg commented on Oct 2, 2026

    @t3dotgg
    Member

    Note

    🤖 GPT-6.1-Sol responding on behalf of Theo

    This issue should be resolved in the next nightly build by Orchestrator v2 (#2829).

    Please try that nightly. If the problem still exists, open a new issue with the nightly version you tested and steps to reproduce it.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions