Skip to content

Opening an active conversation in another VS Code window can silently transfer ownership and allow concurrent turns #38629

Description

@alexchexes

What version of the IDE extension are you using?

openai.chatgpt 26.5727.51351-darwin-arm64

What subscription do you have?

Plus

Which IDE are you using?

VS Code

What platform is your computer?

Darwin 25.6.0 arm64 arm

What issue are you seeing?

Opening an active conversation in another VS Code window can race thread hydration against follower-role discovery. The second window first becomes a follower, then its already-running thread/resume changes it to owner. The original window is demoted to follower, but its app-server process and active turn continue running.

Both UIs can show the original turn as interrupted or stopped even though its process is still active. Sending an ordinary prompt from the new owner then starts a second turn. Two separate codex app-server processes execute turns in the same conversation concurrently and append interleaved records to the same rollout.

The clean repro didn't use any MCP, or Play/continue action. The original turn used only the built-in shell and wait tools. The second turn used no tools.

The extension logs show the race directly:

19:31:27.486  window B maybe_resume_started; previousStreamRole=null
19:31:28.332  window B role=follower
19:31:29.262  window B role=owner
19:31:29.263  window B maybe_resume_success; latest turn status=interrupted
19:31:30.139  window A role owner -> follower

This also matches the shipped webview code: the hydration path checks the stream role before its asynchronous work, but after thread/resume succeeds it marks the conversation streaming and sets the role to owner without rechecking whether an IPC snapshot assigned it as follower in the meantime.

What steps can reproduce the bug?

  1. In one VS Code window (A), create a new Codex thread.

  2. Attach a few images to increase rollout size. In my latest reproduction, 6 PNG screenshots produced ~25 MB rollout. Send them with this prompt:

    This is a test-data seed turn. Do not inspect/describe the attached images. Reply "SEED READY".
    
  3. In the same conversation and window, send:

    This is a concurrency test. Keep this turn active for 20 minutes unless I explicitly send "STOP TEST".
    
    Procedure:
    1. Send one commentary message: Starting the silent built-in wait now.
    2. Start exactly this harmless local command in a PTY:
       /bin/sleep 1200
       
    3. If the shell tool returns a running session before the command completes, immediately wait on that same session for 50 to 55 seconds at a time.
    4. While the command remains active, do not send commentary and do not start another command.
    5. When the command completes, send a final answer stating that the silent wait completed normally.
    6. If the command exits unexpectedly, report that fact and finish. Do not call another tool.
    
    Constraints:
    - Do not call any MCP tool.
    - Do not ask for approval or other human input.
    - Do not create, modify, move, or delete files.
    - Do not make network requests.
    - Use only Codex's built-in shell execution and wait tools.
    - Do not send a final answer before the command completes or I explicitly send "STOP TEST".
    
    
  4. Wait until the shell command is active.

  5. In a separate VS Code window (B), open the same conversation from history. Do not press Play/Stop or any other controls.

  6. Window B's hydration wins the race: in my repro, it silently changed from follower to owner, window A became follower, and both UIs showed the first turn as stopped even though window A's app-server and /bin/sleep were still running.

  7. In window B, type an ordinary prompt and send it:

    Reply "SECOND TURN STARTED"
    
  8. Inspect the rollout and processes. In my latest reproduction, the second turn started at 19:41:17 and completed at 19:41:22. The first app-server's pending wait returned at 19:41:33 and it issued another wait at 19:41:39, while its /bin/sleep 1200 process was still alive. Two app-server processes had the same rollout open and their records were interleaved.

What is the expected behavior?

A conversation must have at most one mutation-capable active turn.

A second VS Code window should attach as a follower. An in-flight hydration must not overwrite a follower role with owner. Any prompt sent from the follower should be routed to the actual owner or rejected, and must not start a concurrent turn through another app-server process.

Additional information

  • Bundled Codex CLI: 0.146.0-alpha.9.2
  • The original incident involved a human-in-the-loop MCP tool used for approvals. Two clean reproductions confirm that neither this tool nor blocking MCP calls in general are required.
  • Payload size appears to widen the snapshot/hydration race. Both image-seeded runs reproduced it, while three smaller clean conversations stayed followers because the owner snapshot arrived before maybe_resume_started.
  • Full conversation IDs, turn IDs, rollout traces, process details, and VS Code extension logs are available privately.
  • This appears related to #34767 and #32793, which report simultaneous turns through other multi-client paths. #37856 covers related IDE ownership handling, but produces the safer opposite outcome: the second client is blocked.

Activity

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

    app-serverIssues involving app server protocol or interfacesbugSomething 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