Skip to content

VS Code queued follow-ups stop processing after completed turn resumes as markedStreaming=true #26413

Open

Description

@duncanmcl

What version of the IDE extension are you using?

26.601.21317

What subscription do you have?

Pro

Which IDE are you using?

VS Code

What platform is your computer?

Linux 6.8.0-117-generic x86_64 x86_64

What issue are you seeing?

The VS Code Codex extension task queue can stop processing queued follow-ups after a completed turn is restored as streaming.

I had multiple queued follow-ups in one thread. One task completed, but later queued tasks did not start. The queued follow-ups were still persisted, so the queue had not actually emptied.

Relevant log pattern:

maybe_resume_success conversationId=<redacted> latestTurnStatus=completed markedStreaming=true

I also saw related queue/steering symptoms:

[queued-followups] Failed to release send lock conversationId=<redacted> error={}
Received app server error error={"code":-32600,"message":"no active turn to steer"}

This appears related to #19690, but this report is specifically about the queued-follow-up processor stopping with messages still persisted.

What steps can reproduce the bug?

  1. Open a thread in the VS Code Codex extension.
  2. Submit a task.
  3. While the task is running, queue multiple follow-up messages in the same thread.
  4. Let one queued task complete.
  5. Reload/restart VS Code or otherwise trigger thread resume.
  6. Check the Codex log.
  7. In the failing case, the latest turn is completed but resume logs markedStreaming=true.
  8. The queued follow-up list still contains messages, but the queue stops processing them.

No code snippet is involved; this appears to be extension/session queue state.

What is the expected behavior?

A completed turn should not be restored as actively streaming.

If queued follow-ups remain and the latest turn is completed, the extension should start the next queued follow-up instead of leaving the queue stopped.

The queue should not require an already-active stream in order to start the next queued turn after a completed turn.

Additional information

Possible fix direction from local debugging:

  • Only mark a resumed/snapshotted conversation as streaming if the latest turn is inProgress.
  • Clear streaming state on terminal turn/completed.
  • Do not restore queued follow-ups with pausedReason after a normal completed turn.
  • Ensure queued follow-up send-lock release succeeds.
  • Treat stale no active turn to steer / active_turn_not_steerable as inactive-turn queue state.
  • Allow starting the next queued follow-up after a completed turn.

Activity

  1. added
    bugSomething isn't working
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on Jun 4, 2026
  2. github-actions commented on Jun 4, 2026

    @github-actions
    Contributor

    Potential duplicates detected. Please review them and close your issue if it is a duplicate.

    Powered by Codex Action

  3. duncanmcl commented on Jun 4, 2026

    @duncanmcl
    Author

    Follow-up after more reproductions:

    The queue stopped again across multiple later reproductions after the stale-streaming fix direction. In these later cases, the logs no longer pointed to the earlier completed-turn-restored-as-streaming issue.

    Observed pattern:

    • The thread resumed successfully.
    • The latest turn was already completed.
    • markedStreaming=false.
    • Queued follow-ups remained persisted.
    • Queue-change broadcasts happened.
    • There was no fresh no active turn to steer.
    • There was no queued-follow-up send failure.
    • There was no send-lock release failure.
    • The worker appeared to remain stuck before attempting queued-follow-up-send-lock-acquire.

    This points to another queue-side readiness blocker.

    A likely remaining issue is that the queued-follow-up readiness predicate appears to require the latest completed turn to contain a renderer item typed exactly agentMessage or a manual contextCompaction before draining queued follow-ups. That seems too strict. A terminal completed turn should be enough to start the next queued follow-up when:

    • the queued message is not paused,
    • the conversation exists,
    • the current client owns the thread rather than being a follower,
    • and the latest turn is not inProgress.

    If a completed turn has a different renderer item shape, the queue can silently retry forever without ever attempting the send lock.

    Suggested additional fix:

    Do not require latestTurn.items.some(item => item.type === "agentMessage") before draining queued follow-ups. For queued follow-ups, a terminal completed latest turn should allow the next queued message to start. Retry should be reserved for genuinely transient not-ready states, such as an active inProgress turn or missing conversation state.

  4. duncanmcl commented on Jun 4, 2026

    @duncanmcl
    Author

    I confirmed another queued-follow-up failure mode that appears tied to the VS Code webview/renderer lifecycle.

    In one reproduction, a queued task completed while VS Code was not the foreground application:

    • previous queued turn completed at 2026-06-04T17:16:04.833Z UTC (2026-06-05 02:16:04 JST)
    • the next queued turn did not start until 2026-06-04T17:25:14.578Z UTC (2026-06-05 02:25:14 JST)
    • I switched focus back to VS Code around that time
    • the handoff gap was about 549.7s
    • adjacent normal queued handoffs in the same thread were about 1.1s

    This suggests the queued-follow-up drain path is still dependent on webview/renderer focus or lifecycle. Local bundle inspection showed the queue worker was running in the webview and originally consumed turn completion through a renderer atom/effect path. I also found a stale-state edge where the queue guard could see latestTurn.status === "inProgress" after turn/completed had already cleared the stream role, causing it to rely on a delayed retry timer.

    The local workaround I tested was:

    • only block on latestTurn.status === "inProgress" if the stream role still says the client owns an active stream
    • subscribe the queue worker directly to the manager addTurnCompletedListener instead of draining through the turn-completed atom watcher

    After applying those local changes, the next observed handoffs in the same queue returned to immediate timing, e.g.:

    • 2026-06-04T17:36:40.526Z complete -> 2026-06-04T17:36:40.666Z next start (0.1s)
    • later handoffs were again around 1.1s

    I do not think a webview-only fix is fully robust. The durable upstream fix should make queued-follow-up draining, or at least the wake/retry scheduling after turn/completed, run from the extension host/app-server side rather than depending on a focused/active webview renderer.

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 isn't workingextensionIssues related to the VS Code extensionsessionIssues involving session (thread) management, resuming, forking, naming, archiving

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions