You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
0.147-alpha (Windows): App keeps thread writer for every opened thread until full app exit; no attach path for external clients #37450
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.
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:
Open an existing thread in the Windows desktop app.
Run a second app-server/helper that accepts the already-paired mobile Remote connection.
Open the same thread from iOS Remote and try to load or send a message.
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.
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.
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
An automation client created a thread on its own app-server and ran it. Later turns came from other clients.
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.
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.
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.
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.
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:
The app unsubscribes from threads it navigates away from, so the existing 60 s unload applies.
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.
Activity
github-actions commentedon Aug 7, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
already has an active writerafter latest update #37403Powered by Codex Action
1063716500-sketch commentedon Aug 13, 2026
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:
26.803.10989.0codex-cli 0.147.0-alpha.6.6Reproduction:
thread/resumefails with: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.
boombx403-byte commentedon Aug 18, 2026
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:
No raw data is needed; please redact any private repository paths before sharing results.
mrdnlabs-kevin commentedon Oct 9, 2026
Same behaviour on macOS, on a much newer build, and it also blocks
thread/archive, not only resume.Environment
codex-cli 0.162.0-alpha.2CODEX_HOME: the managed app-server daemon (0.162.0) and an automation client running its owncodex app-serverover stdioWhat happened
thread/resumefrom the app's bundled app-server, client "Codex Desktop"). It ran one more turn there, which finished normally.lsofshowed the app's bundledcodex app-serverholding~/.codex/thread-writer-locks/<id>.lockand the rollout file open.thread/archivefor the thread failed:Codex RPC -32600: thread <id> already has an active writer(10 retries over ~90 min)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.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/archivefrom 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.
dzshzx commentedon Oct 13, 2026
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):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: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.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 orthread/unsubscribed, the lock file disappears at about 60 s and the thread leavesthread/loaded/list.codex execalways 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, notcodex 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 sendinitialize,initialized, thenturn/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, callthread/resumefirst, sinceturn/startalone doesn't subscribe the connection.Either of these would remove the conflict for automation:
codex exec(and the SDK'sresumeThread) attach to a running local daemon, the way the TUI already does, instead of failing on the writer lock.