Repository navigation
Open in app allows two app-server processes to write the same rollout, merging divergent histories after restart #33241
Description
What version of the Codex App are you using (From “About Codex” dialog)?
Codex App 26.707.72221 (bundle com.openai.codex)
Bundled app-server: codex-cli 0.144.2
What subscription do you have?
Pro 20x
What platform is your computer?
Darwin 25.3.0 arm64 arm
Chrome extension: ChatGPT 1.2.27203.26575
What issue are you seeing?
Starting a local Codex task from the ChatGPT Chrome extension and selecting Open in app leaves both the browser extension and Codex App able to continue the same thread.
The two surfaces use different app-server processes and maintain independent in-memory conversation histories, while both processes append to the same rollout JSONL under the same thread ID. lsof showed two different codex app-server PIDs holding writable file descriptors for that rollout at the same time.
Before restart, each surface only sees its own branch. After restarting Codex App, the rollout is replayed from disk and messages from both divergent branches appear merged into one conversation.
This is not Edge-specific: I first observed it with Edge and reproduced it with Chrome.
What steps can reproduce the bug?
- Start a local Codex task from the ChatGPT Chrome extension.
- Send one or more messages.
- Select Open in app.
- Continue sending messages independently from both Chrome and Codex App.
- Ask each surface about the preceding messages. Each surface answers from a different conversation history.
- Inspect the rollout with
lsof. Two different app-server processes have writable file descriptors for the same rollout JSONL. - Restart Codex App.
- Open the task again.
- Messages from both previously divergent histories are now displayed as one merged conversation.
Observed thread ID:
019f63f5-db08-79a1-9b55-722fb497cc1a
Observed rollout:
$CODEX_HOME/sessions/2026/07/15/rollout-2026-07-15T12-08-04-019f63f5-db08-79a1-9b55-722fb497cc1a.jsonl
What is the expected behavior?
Open in app should perform an exclusive handoff. After the handoff, the browser surface should become read-only or stop its app-server session. Alternatively, both surfaces should attach to one shared runtime.
If concurrent attachment is unsupported, Codex should reject the second writer or fork it to a new thread ID and rollout file. Two processes should never independently append divergent histories to the same rollout.
Additional information
Archiving the task in Codex App also causes cross-surface inconsistencies: the browser process can remain active while the App considers the task archived, and a subsequent Open in app no longer reopens it normally.
A screenshot of the merged history after restarting the App is available. The text above describes the observable result because the issue template prefers text over screenshots.
Related issues, but none appears to cover the same two-writer/cold-replay behavior:
- Active goals can auto-continue concurrently across app-server processes #32793 reports process-local ownership allowing concurrent work across two app-server processes, but only for automatic goal continuation.
- Codex mobile can open the same VS Code thread as a concurrent assistant/tool lineage while a turn is active #31114 reports concurrent lineages under one thread ID, but explicitly observed one process and one writer.
- App-server TUI does not fully reflect external live turns on shared thread #15320 states that concurrent clients on one thread are currently unsupported; in this reproduction, Open in app creates that unsupported topology without handing off, rejecting, or forking the original client.
- Sync CLI and app-server sessions #14722 is the broader request for CLI/app-server session synchronization.
- Codex App: add manual refresh button (or auto-sync) for archived and cross-surface conversations #11907 covers archive and cross-surface refresh gaps.
Activity
github-actions commentedon Jul 15, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
starSumi commentedon Aug 2, 2026
I reproduced the same two-writer failure on Windows without Open in app or Remote Control. Two independent TUI processes running
codex resume <same-thread-id>can both attach to and append to the same rollout.Environment:
codex-cli 0.146.0The resulting rollout remained syntactically valid: all 1,030 JSONL records parsed successfully. It was nevertheless semantically interleaved. A lifecycle scan found three cases where a new
task_startedappeared before the preceding turn emitted a terminal event. Each TUI initially saw only its own in-memory branch; resuming later replayed the combined on-disk history.This narrows the failure beyond the cross-surface handoff in the original report. At current
main(2b5bdcf67547860f2e5c5a605009a70026796b2b),RolloutRecorder::newopens a resumed rollout throughopen_rollout_for_append, but writer ownership is process-local and there is no cross-process exclusion around the rollout append path.I tested a local prototype that acquires an OS-backed exclusive lock on a per-rollout sidecar before materialization or append and holds it for the writer lifetime. A pre-existing sidecar is harmless because process termination releases the OS lock; a live second writer receives a
WouldBlockerror. The regression covers a stale sidecar, rejection of a concurrent resume, release on shutdown, and a successful later resume.Validation on the current
mainbase:Would maintainers prefer the per-rollout recorder lock as the admission boundary, or should a competing
resumebe rejected or redirected at a higher thread-management layer? I did not open a PR because the contribution policy requires an explicit maintainer invitation.seqis commentedon Aug 23, 2026
Reproduced on Linux Mint 22.3 x86_64.
Environment:
Steps:
The desktop app shows: “This is open in another app. Close it there to continue here.”
Closing the panel does not release the conversation. Disabling the extension releases it; the desktop app can then open the conversation. Re-enabling the extension afterward does not take ownership back.
Expected behavior: closing the panel should release ownership, or the desktop app should provide a safe “Take over” action. Users should not need to disable the extension or quit the browser.