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

Open 💬 3 comments Opened Jun 4, 2026 by duncanmcl
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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.

View original on GitHub ↗

3 Comments

github-actions[bot] contributor · 2 months ago

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

  • #25094

Powered by Codex Action

duncanmcl · 2 months ago

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.

duncanmcl · 2 months ago

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.