Skip to content

Open in app allows two app-server processes to write the same rollout, merging divergent histories after restart #33241

Open

Description

@Lin-Guanguo

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?

  1. Start a local Codex task from the ChatGPT Chrome extension.
  2. Send one or more messages.
  3. Select Open in app.
  4. Continue sending messages independently from both Chrome and Codex App.
  5. Ask each surface about the preceding messages. Each surface answers from a different conversation history.
  6. Inspect the rollout with lsof. Two different app-server processes have writable file descriptors for the same rollout JSONL.
  7. Restart Codex App.
  8. Open the task again.
  9. 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:

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    app-serverIssues involving app server protocol or interfaces
    on Jul 15, 2026
  2. github-actions commented on Jul 15, 2026

    @github-actions
    Contributor

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

    Powered by Codex Action

  3. starSumi commented on Aug 2, 2026

    @starSumi

    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.0
    • Windows 10 22H2, build 19045
    • Two standalone Codex TUI processes

    The 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_started appeared 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::new opens a resumed rollout through open_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 WouldBlock error. The regression covers a stale sidecar, rejection of a concurrent resume, release on shutdown, and a successful later resume.

    Validation on the current main base:

    just test -p codex-rollout
    110 tests run: 110 passed
    
    just fix -p codex-rollout
    Finished successfully with no warnings
    

    Would maintainers prefer the per-rollout recorder lock as the admission boundary, or should a competing resume be rejected or redirected at a higher thread-management layer? I did not open a PR because the contribution policy requires an explicit maintainer invitation.

  4. seqis commented on Aug 23, 2026

    @seqis

    Reproduced on Linux Mint 22.3 x86_64.

    Environment:

    • Kernel: 7.0.0-30-generic
    • ChatGPT desktop app: 26.818.41705
    • Bundled Codex app-server: 0.149.0-alpha.4.1
    • Brave: 1.93.138 / Chromium 151.1.93.138
    • ChatGPT browser extension: 1.2.27268.51612
    • Browser plugin runtime: 26.818.41705

    Steps:

    1. Start a conversation in the ChatGPT/Codex extension side panel in Brave.
    2. Close the panel while leaving Brave open.
    3. Open the same conversation in the desktop app.

    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.

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

    appIssues related to the Codex desktop appapp-serverIssues involving app server protocol or interfacesbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions