Opening an active conversation in another VS Code window can silently transfer ownership and allow concurrent turns
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?
- In one VS Code window (A), create a new Codex thread.
- Attach a few images to increase rollout size. In my latest reproduction, 6 PNG screenshots produced ~25 MB rollout. Send them with this prompt:
``text``
This is a test-data seed turn. Do not inspect/describe the attached images. Reply "SEED READY".
- In the same conversation and window, send:
```text
This is a concurrency test. Keep this turn active for 20 minutes unless I explicitly send "STOP TEST".
Procedure:
- Send one commentary message: Starting the silent built-in wait now.
- Start exactly this harmless local command in a PTY:
/bin/sleep 1200
- 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.
- While the command remains active, do not send commentary and do not start another command.
- When the command completes, send a final answer stating that the silent wait completed normally.
- 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".
```
- Wait until the shell command is active.
- In a separate VS Code window (B), open the same conversation from history. Do not press Play/Stop or any other controls.
- 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/sleepwere still running. - In window B, type an ordinary prompt and send it:
``text``
Reply "SECOND TURN STARTED"
- 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 1200process 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.
5 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
#38560 is related at the multi-client ownership layer, but the failure and outcome are different. In #38560, the competing
thread/resumeis rejected withalready has an active writer, followed by a high-frequency retry/UI loop. In this issue,thread/resumesucceeds after the second client has already been assignedrole=follower, overwrites that role withowner, demotes the original owner, and allows a real second turn while the first app-server is still executing. There is no active-writer rejection or retry loop here.Your clean repro where two app-server processes keep writing interleaved records to the same rollout is a useful recovery/safety boundary.
I maintain Codex Rescue, a local read-only session diagnostic/recovery tool. I’d like to check whether the current alpha treats this kind of history conservatively rather than assuming one authoritative turn stream.
If you still have one of the reproduced rollouts and are comfortable testing a third-party local tool, would you be willing to run:
Only use
--latestif that affected rollout is still your latest one. No salvage/repair needed.Sanitized status/findings only please — no raw JSONL, prompts, repository content, paths, IDs, tool output, credentials, or secrets.
https://github.com/shleder/codex-rescue
@shleder I can try later, but it will probably be faster if you reproduce that yourself. In addition to answering your tool question directly, this will add one independent reproduction to this very issue, which increases the chance it is addressed sooner.
Am geting {"detail":"Bad Request"}