Skip to content

0.147-alpha (Windows): App keeps thread writer for every opened thread until full app exit; no attach path for external clients #37450

Open

Description

@imaximov

0.147-alpha (Windows): App keeps thread writer for every opened thread until full app exit; no attach path for external clients

Version: codex-cli 0.147.0-alpha.6.5 (Desktop App bundle), Windows 11 Pro 10.0.26200

Summary

Since 0.147 introduced single-writer thread persistence, codex exec resume fails with:

ERROR codex_core::session::session: failed to initialize thread persistence:
thread-store conflict: thread already has an active writer
whenever the Desktop App has ever opened that thread in the current app run. The writer is not released when the user navigates away from the thread — only a full app exit (including the tray process) releases it.

Reproduction (verified step by step)

Open thread X in the Desktop App → codex exec resume X "hi" → thread-store conflict. Expected? Arguably yes.
Switch to a different thread in the App (thread X no longer displayed) → exec resume X → still conflict. Waiting several minutes does not help.
Close the App window, tray process still running → still conflict.
Fully quit the App (tray → exit) → exec resume X succeeds.
Expected: the writer for thread X is released when the App navigates away from it (or after a short idle period), so CLI/SDK automation can continue threads the user merely viewed.

Impact: any automation built on codex exec resume / the TypeScript SDK resumeThread() (e.g. local review loops that continue a long-lived thread) cannot run while the Desktop App is open if the target thread was viewed at any point. Users must fully restart the App before every automated turn.

Related gap (Windows): there is currently no way to avoid the conflict by attaching to the App as a client instead:

codex app-server daemon start and codex remote-control start → Error: codex app-server daemon lifecycle is only supported on Unix platforms
The Desktop App on Windows does not expose the control socket (~/.codex/app-server-control/app-server-control.sock is absent; no listening TCP port or named pipe from the codex processes).
Either releasing writers on thread switch or providing a Windows daemon/control socket (so external tools share the App's writer via the app-server protocol) would unblock this workflow.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    windows-osIssues related to Codex on Windows systems
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    execIssues related to the `codex exec` subcommand
    app-serverIssues involving app server protocol or interfaces
    on Aug 7, 2026
  2. github-actions commented on Aug 7, 2026

    @github-actions
    Contributor

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

    Powered by Codex Action

  3. 1063716500-sketch commented on Aug 13, 2026

    @1063716500-sketch

    I can confirm the same active-writer behavior on Windows in a mobile Remote Control workflow, and it compounds the system-proxy problem described in #29958.

    Environment observed during reproduction:

    • Windows 10 Pro 22H2, build 19045.6466, x64
    • Windows app package: 26.803.10989.0
    • Bundled app-server: codex-cli 0.147.0-alpha.6.6
    • Mobile client: iOS; version not present in the Windows host logs

    Reproduction:

    1. Open an existing thread in the Windows desktop app.
    2. Run a second app-server/helper that accepts the already-paired mobile Remote connection.
    3. Open the same thread from iOS Remote and try to load or send a message.
    4. The Remote request reaches the helper, but thread/resume fails with:
    failed to initialize thread persistence
    thread-store conflict: thread already has an active writer
    Failed to create session
    

    The iOS UI then reports that the Codex server returned an error and cannot load or send messages. Fully exiting the Windows desktop app releases the conflict; with only the helper running, the same mobile thread works.

    In this environment the helper exists only because the desktop Remote WebSocket does not reliably use the Windows system proxy. Therefore the workaround forces a mutually exclusive choice: keep the desktop UI open, or use mobile Remote, but not both.

    Expected: either the desktop app should directly own the functioning Remote connection, or Desktop/Remote app-server instances should have a supported ownership handoff/attach path. A clear host-conflict error would also be preferable to the generic mobile “server returned an error.”

    No raw databases, complete logs, pairing data, host IDs, thread IDs, usernames, project names, or tokens are included.

  4. boombx403-byte commented on Aug 18, 2026

    @boombx403-byte

    Hi @imaximov, I'm testing a local-first, read-only diagnostic tool (Codex Rescue Alpha5) against local session persistence and rollout anomalies. It evaluates schema compatibility, tool correlation, and state consistency without modifying the original rollout or SQLite databases.

    If the affected session is still present locally, you can run:

    pip install codex-rescue==0.1.0a5
    codex-rescue doctor --latest

    No raw data is needed; please redact any private repository paths before sharing results.

  5. mrdnlabs-kevin commented on Oct 9, 2026

    @mrdnlabs-kevin

    Same behaviour on macOS, on a much newer build, and it also blocks thread/archive, not only resume.

    Environment

    • macOS 26.4.1 (Apple silicon)
    • Desktop app 26.1002.52244, bundled app-server codex-cli 0.162.0-alpha.2
    • On the same machine and CODEX_HOME: the managed app-server daemon (0.162.0) and an automation client running its own codex app-server over stdio

    What happened

    1. An automation client created a thread on its own app-server and ran it. Later turns came from other clients.
    2. The desktop app opened the thread (thread/resume from the app's bundled app-server, client "Codex Desktop"). It ran one more turn there, which finished normally.
    3. The app then kept the writer for that thread for over two hours with no turn running, until the app was quit. lsof showed the app's bundled codex app-server holding ~/.codex/thread-writer-locks/<id>.lock and the rollout file open.
    4. During that time every thread/archive for the thread failed:
      • from the automation client's stdio app-server: Codex RPC -32600: thread <id> already has an active writer (10 retries over ~90 min)
      • from a second Desktop instance connected over SSH to the managed daemon: the archive request internally resumes the thread and fails with failed to initialize thread persistence: thread-store conflict: thread <id> already has an active writer. The UI showed nothing useful, and the thread stayed unarchived.
    5. Quitting and reopening the desktop app released the lock straight away, and archiving from the other client then worked.

    Expected
    Either the app releases the writer for a thread it isn't showing and that has no active turn (as the original report suggests), or a thread/archive from another app-server succeeds or is forwarded to the writer. Archiving an idle thread shouldn't need the writer slot at all. At minimum, the archiving client should get an error saying the thread is open in another Codex process, instead of an archive that silently fails.

    Frequency
    Over 3 days our automation hit the conflict 13 times on archive. Twelve cleared on their own within ~20 min. The one held by the desktop app needed an app restart.

  6. dzshzx commented on Oct 13, 2026

    @dzshzx

    Still reproducible on Linux (WSL2) with codex-cli 0.162.1. The Desktop app attaches to the managed app-server daemon (app-server --listen unix://), and every thread it has opened stays loaded there. codex exec resume <id> then fails before any model call (exit 1, no JSON events on stdout):

    thread/resume failed: thread <id> already has an active writer (code -32600)
    

    Reading rust-v0.162.1 (092d3ac), the lock itself works as designed. The problem is how long it is held, and that no path hands it off:

    • The writer lock is an OS file lock taken when a thread is loaded (thread-store/src/local/live_writer.rs:33,46, rollout/src/writer_lock.rs:43-86). It is released only when the thread is shut down (live_writer.rs:169-207), so its lifetime is the thread's load lifetime. Whether a turn is running does not matter.
    • The daemon unloads a thread only after it has had no subscribed connection and no activity for thread_unload_delay_secs (default 60): app-server/src/request_processors/thread_lifecycle.rs:56-62, :374-400. Experiments confirm this. For a thread created over the control socket, then either disconnected or thread/unsubscribed, the lock file disappears at about 60 s and the thread leaves thread/loaded/list.
    • So the hold comes from the app keeping its subscription. Switching threads or closing the window doesn't unsubscribe; only quitting the app does, matching the steps in this issue.
    • codex exec always runs an in-process app-server (exec/src/lib.rs:1022, InProcessAppServerClient) and cannot attach to the daemon. codex --remote unix:// exec … is rejected with "--remote unix:// is only supported for interactive TUI commands, not codex exec" (cli/src/main.rs:1152-1157). Only the TUI auto-detects the daemon socket.

    Workaround that works today: talk to the daemon directly. Connect to $CODEX_HOME/app-server-control/app-server-control.sock (JSON-RPC over WebSocket) and send initialize, initialized, then turn/start {threadId, input}. The turn runs on the daemon's writer, lands in the same thread history, and keeps running after the client disconnects. To receive events, call thread/resume first, since turn/start alone doesn't subscribe the connection.

    Either of these would remove the conflict for automation:

    1. The app unsubscribes from threads it navigates away from, so the existing 60 s unload applies.
    2. codex exec (and the SDK's resumeThread) attach to a running local daemon, the way the TUI already does, instead of failing on the writer lock.
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 workingexecIssues related to the `codex exec` subcommandsessionIssues involving session (thread) management, resuming, forking, naming, archivingwindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions