Skip to content

[macOS][regression] Desktop cannot resume Remote Control / CLI thread: already has an active writer after latest update #37403

Open

Description

@xkun1

Summary

After updating the ChatGPT Desktop client on macOS on August 7, 2026, an existing workflow that previously worked stopped working:

  • During off-hours, I use ChatGPT mobile Remote Control to continue a Codex CLI thread on my Mac.
  • During the day, I open the same thread in the Desktop client and continue from there.

After the latest Desktop update, the Desktop/CLI handoff fails because the thread is reported as already having an active writer.

This appears to be a regression in thread ownership / writer handoff between the Desktop-bundled app-server/runtime and the CLI/Remote Control runtime.

Environment

  • Platform: macOS (iMac)
  • Standalone CLI:
    codex-cli 0.147.0
    
  • ChatGPT Desktop bundled Codex runtime:
    /Applications/ChatGPT.app/Contents/Resources/codex --version
    codex-cli 0.147.0-alpha.6.5
    
  • The issue started immediately after installing the latest ChatGPT Desktop update on August 7, 2026.
  • The same Remote Control / CLI / Desktop workflow worked before that update.

Steps to reproduce

  1. Keep Codex Remote Control enabled on the Mac.
  2. Use ChatGPT mobile Remote Control to open/continue a Codex CLI thread.
  3. Later, on the same Mac, try to resume/open that same thread from the Desktop client.
  4. Observe that the thread cannot be resumed because another writer is considered active.

Actual behavior

The resume attempt fails with an error like:

Failed to resume session from ~/.codex/sessions/YYYY/MM/DD/rollout-...jsonl:
thread/resume failed during TUI bootstrap:
thread/resume failed: thread <redacted-thread-id> already has an active writer (code -32600)

A key observation:

  • With the Desktop client running, the thread hits the already has an active writer conflict.
  • If I fully quit the Desktop client, the same session can immediately be opened/resumed normally from the standalone CLI.

The session JSONL itself therefore appears intact; the problem looks like writer ownership/lifecycle rather than session corruption.

Expected behavior

Remote Control is intended to let the same work continue across mobile and desktop usage. A thread used through Remote Control / CLI should be able to hand off cleanly to Desktop when the user returns to the Mac.

Expected flow:

Mobile Remote Control / CLI thread
        ↓
user stops actively using mobile
        ↓
Desktop resumes the same thread
        ↓
ownership/writer handoff succeeds

The user should not need to disable Remote Control, kill app-server processes, fork the conversation, or create a new thread just to switch between mobile and Desktop.

Impact

This breaks a core daily workflow:

  • Desktop during work hours
  • Mobile Remote Control after work
  • Same Codex thread/context across both surfaces

Because Remote Control needs to remain enabled, simply turning it off is not a practical workaround.

Regression evidence

The behavior changed immediately after the latest Desktop update. No CLI configuration or session files were changed at the time.

The standalone CLI and Desktop-bundled runtime are different builds:

Standalone: 0.147.0
Desktop bundled: 0.147.0-alpha.6.5

This may be related to stricter single-writer enforcement without a corresponding Desktop ↔ Remote Control ownership handoff.

Privacy note

The local username, exact session path, thread ID, repository/project names, and conversation contents are intentionally omitted. I can provide additional sanitized diagnostics if maintainers request them.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    app-serverIssues involving app server protocol or interfaces
    on Aug 7, 2026
  2. imaximov commented on Aug 7, 2026

    @imaximov

    Reproduced on Windows 11 with the same versions (Desktop bundled codex-cli 0.147.0-alpha.6.5), and it does not require Remote Control — merely viewing a thread in the Desktop app takes its writer and never releases it:

    Open thread X in the Desktop app → codex exec resume X "hi" → thread-store conflict: thread X already has an active writer
    Switch to a different thread in the app (X no longer displayed) → still conflict (waiting several minutes doesn't help)
    Close the app window, tray process still running → still conflict
    Fully quit the app (tray → exit) → exec resume X succeeds immediately
    So the writer appears to be held per-opened-thread for the lifetime of the app process, rather than released on navigation away. Same conclusion as OP: ownership/lifecycle issue, session file intact.

    Impact on Windows is worse than macOS: there is no attach alternative — codex app-server daemon start / codex remote-control start both fail with codex app-server daemon lifecycle is only supported on Unix platforms, and the Desktop app exposes no control socket (default ~/.codex/app-server-control/app-server-control.sock absent), so external automation (exec/SDK resumeThread) has no way to share the app's writer and must fully restart the app between turns.

  3. leadingproblemsolver commented on Aug 7, 2026

    @leadingproblemsolver

    The reproduction suggests the missing primitive is not just "release the writer sooner"; it is an explicit, inspectable writer lease with fencing semantics.

    If thread ownership is represented only by "some runtime has an active writer", handoff bugs are hard to distinguish from stale ownership. I would persist something like:

    thread_id
    lease_id
    owner_runtime_id
    owner_surface = desktop | cli | remote
    acquired_at
    last_heartbeat_at
    lease_epoch
    state = active | releasing | released | expired
    release_reason
    

    Then handoff can be a compare-and-swap operation rather than implicit process lifetime:

    1. Desktop owns epoch N.
    2. Remote/CLI requests handoff.
    3. Desktop quiesces writes and records its final durable cursor/turn.
    4. Lease N is released.
    5. New owner acquires epoch N+1.
    6. Any late write carrying epoch N is rejected as stale.

    The lease_epoch/fencing token matters. A timeout alone can otherwise create the opposite failure: Desktop looks dead, CLI takes ownership, then the old writer wakes and both believe they own the thread.

    For UX/debugging, the conflict should expose who owns the lease and whether it is live: held by Desktop pid/runtime X, heartbeat 1.2s ago, versus stale lease last seen 12m ago. That makes "fully quit Desktop" a diagnosable lease-lifecycle bug rather than the only recovery mechanism.

    This is adjacent to the operational-state model I have been building in https://github.com/leadingproblemsolver/living-context-engine: durable state is useful only when ownership/status claims retain provenance and can be reconciled against current reality. LCE itself does not implement writer leasing, but the same principle applies here: make the ownership claim a first-class record with evidence and lifecycle, not an implicit inference from an open process/thread.

  4. IA21 commented on Aug 9, 2026

    @IA21

    Same issue on Fedora 43. If vs code with codex extension is running, codex resume in cli will exit with this error even if the chat is not ongoing in vs code, even if the codex pane in vs code is hidden. It only works when I fully close vs code. This is a bad regression, I keep an eye on token usage through the codex cli app because the vs code extension /status command is useless, it does not tell me anything besides session ID. I need to query /status in codex cli from time to time to keep track of token consumption. I cannot exit vs code each time I need to check that.

  5. kerenkxs2 commented on Aug 9, 2026

    @kerenkxs2

    This error is happening to me too. Codex resume is basically useless now.

  6. Eslsamu commented on Aug 11, 2026

    @Eslsamu

    I'm seeing what appears to be the same underlying writer-ownership regression, but in a slightly different and very reproducible Remote Control scenario on macOS.

    Impact / severity

    For my workflow, this currently makes Remote Control effectively unusable.

    The primary reason I use Remote Control is to leave active Codex CLI threads running on my development machine and then monitor or steer those same live threads from my iPhone when I'm away from the computer.

    At the moment, Remote Control only works for threads that are not open in the CLI. As soon as a thread is actually active in the CLI — i.e. exactly the thread I would want to control remotely — the mobile client can no longer load it.

    So while the Remote Control server itself is reachable and inactive threads work, the core live-session use case is completely blocked.

    Scenario

    I start Codex normally in the CLI and then expose the machine to Remote Control, either by:

    1. Opening the ChatGPT/Codex desktop app with remote control enabled, or
    2. Running:
    codex features enable remote_control
    codex remote-control

    I can then see my local Codex threads from the ChatGPT iPhone app.

    Behavior

    Threads that exist locally but are not currently open in a Codex CLI process work correctly from the iPhone app. I can open them, see their messages, and interact with them.

    However, any thread that is currently open/running in the CLI fails when opened from the iPhone app.

    The iPhone shows:

    Error loading messages: Codex server returned an error.
    

    At the same time, the codex remote-control process logs:

    ERROR codex_core::session::session: failed to initialize thread persistence:
    thread-store conflict: thread <thread-id> already has an active writer
    
    ERROR codex_core::session: Failed to create session:
    thread-store conflict: thread <thread-id> already has an active writer
    

    This is deterministic:

    • Thread not open in CLI → works remotely.
    • Open the thread in CLI → remote/mobile access fails with already has an active writer.
    • Exit that CLI thread → it becomes remotely accessible again.

    Expected behavior

    Remote Control should be able to attach to / proxy the existing live CLI session rather than attempting to become a second persistent writer.

    The main use case for Remote Control is leaving an active CLI Codex session running on the development machine and then viewing or steering that same live session from another device.

    Requiring the CLI session to be closed before Remote Control can access it defeats that workflow entirely. In practice, this means Remote Control is currently unusable for controlling active CLI work, even though inactive threads remain accessible.

    This occurs regardless of whether Remote Control is started through the desktop app or directly with codex remote-control.

  7. xiaoguan002 commented on Aug 11, 2026

    @xiaoguan002

    I reproduced the same regression in a Desktop-over-SSH workflow on macOS and captured the lock ownership and recovery path.

    Environment

    • Local Desktop build: 26.803.61601
    • Remote macOS host
    • Remote Desktop bundled runtime observed: 0.147.0-alpha.6.5
    • Managed standalone app-server after recovery: 0.147.0
    • Thread size: approximately 47 MB

    Reproduction

    1. A thread on the remote Mac had already completed its latest turn.
    2. The remote ChatGPT Desktop app remained running with that thread previously opened.
    3. Opening the same remote thread from another Mac through its saved SSH host produced:
      thread <redacted> already has an active writer
      
    4. A read-only thread/read request succeeded, but the automatic thread/resume performed by Desktop failed with JSON-RPC -32600.

    Lock evidence

    On the remote host, lsof showed the Desktop-bundled codex app-server child holding both:

    • the thread's rollout JSONL; and
    • ~/.codex/thread-writer-locks/<thread-id>.lock.

    Killing only that child was not sufficient: the parent ChatGPT process immediately respawned it and the new child reacquired the same writer lock. Navigating away from the thread also did not release ownership.

    Fully quitting the remote ChatGPT Desktop process released the lock. A managed standalone app-server then resumed the thread successfully from the SSH client. The local Desktop log showed:

    thread/resume errorCode=null
    maybe_resume_success
    resumeState=resumed
    streamRole=owner
    

    The first successful resume took about 70 seconds because the rollout was large, but the session data was intact.

    Conclusion / workaround

    This confirms a writer-lifecycle/handoff problem rather than session corruption or an SSH transport failure. The current reliable workaround is to fully quit the Desktop process that owns the thread, then resume through the other client.

    Because thread/read still works while another writer owns the thread, Desktop could also provide a visible Open read-only fallback instead of automatically attempting thread/resume and failing.

  8. YoungPhlo commented on Aug 11, 2026

    @YoungPhlo

    Adding a maintainer oriented framing: the single writer invariant itself makes sense. The rollout JSONL is canonical and the SQLite thread history is derived from it, so two independent writers could corrupt ordering, compaction, or projection state.

    The missing primitive is a usable ownership lifecycle. When a local runtime already owns a thread, a second surface should:

    • attach to or forward controls to the existing local writer when that owner is reachable;
    • otherwise offer an explicit read-only transcript view instead of automatically attempting resume;
    • show actionable owner information in conflicts: surface, runtime, PID or endpoint where appropriate, acquisition time, and whether a handoff is possible;
    • support an explicit quiesced handoff: flush the current writer, release its lease, then allow the new surface to acquire it.

    That preserves the safety property while avoiding the current requirement to fully quit Desktop or another host process just to inspect or continue the same thread.

    A global history.persistence = none setting would not address this error and would trade away the resume/fork/rollback benefits of durable thread history.

    The useful control here is writer ownership and handoff.

    Specs:

    Ubuntu 24.04.3 LTS
    Linux 6.17.0-35-generic, x86_64
    24 CPU cores, 54 GiB RAM
    Standalone Codex CLI: 0.147.0
    Node.js: v24.8.0
    
  9. vigneshgurusamy commented on Aug 11, 2026

    @vigneshgurusamy

    I can reproduce the same issue on Linux.

    Environment:

    • Ubuntu 24.04.3 LTS
    • Codex CLI/app-server: 0.147.0
    • Remote Control enabled
    • ChatGPT mobile app on Android

    Behavior:

    • Projects and session lists load correctly.
    • Completed/inactive sessions load their conversations.
    • Active/running sessions appear in the list, but opening them on mobile fails with:
      Error loading messages: Codex server returned an error.
    • After the CLI releases the active session, the conversation becomes accessible remotely.

    The host app-server log repeatedly reports:

    failed to initialize thread persistence:
    thread-store conflict: thread [REDACTED] already has an active writer

    Failed to create session:
    thread-store conflict: thread [REDACTED] already has an active writer

    The affected rollout file exists and contains valid JSONL. This appears to be a writer-ownership or handoff problem where Remote Control tries to create a second writer instead of attaching to the already-running CLI session.

  10. johnolusetire commented on Aug 11, 2026

    @johnolusetire

    I don't know if this is the same error i had earlier but when i got this, i basically went to the .codex directory and deleted the lock file associated with chat i wanted to resume. never had this issue until last week.

    This was on the VS Code Server Extension on a remote Linux machine

    ls ~/.codex
    archived_sessions config.toml history.jsonl logs_2.sqlite memories_1.sqlite session_index.jsonl thread-writer-locks
    attachments config.toml.backup-20260729-180008 installation_id logs_2.sqlite-shm models_cache.json sessions tmp
    auth.json db-backup-20260727 ipc logs_2.sqlite-wal plugins shell_snapshots version.json
    cache goals_1.sqlite log memories rules skills

    ls ~/.codex/thread-writer-locks/
    019ef4ae-30da-7ac0-9a2f-f421eb61610c.lock 019ff2cd-fe3d-7443-896f-31a87daf5f2e.lock

  11. Eslsamu commented on Aug 12, 2026

    @Eslsamu

    I don't know if this is the same error i had earlier but when i got this, i basically went to the .codex directory and deleted the lock file associated with chat i wanted to resume. never had this issue until last week.

    This was on the VS Code Server Extension on a remote Linux machine

    ls ~/.codex archived_sessions config.toml history.jsonl logs_2.sqlite memories_1.sqlite session_index.jsonl thread-writer-locks attachments config.toml.backup-20260729-180008 installation_id logs_2.sqlite-shm models_cache.json sessions tmp auth.json db-backup-20260727 ipc logs_2.sqlite-wal plugins shell_snapshots version.json cache goals_1.sqlite log memories rules skills

    ls ~/.codex/thread-writer-locks/ 019ef4ae-30da-7ac0-9a2f-f421eb61610c.lock 019ff2cd-fe3d-7443-896f-31a87daf5f2e.lock

    I tested the lock-file workaround mentioned above.

    Deleting the corresponding file from:

    ~/.codex/thread-writer-locks/

    does make the thread accessible through Remote Control again.

    However, this does not solve the underlying issue. It simply changes which process owns the thread.

    After deleting the lock and opening the thread remotely:

    • Remote Control / iPhone can access and interact with the thread again.
    • Trying to open that same thread in the CLI then fails with the same already has an active writer error, this time on the CLI side.

    So the behavior appears to be:

    • CLI owns thread → Remote Control cannot access it.
    • Delete lock / Remote Control takes ownership → CLI cannot access it.
    • Close whichever process currently owns the thread → the other side can access it.

    This means the lock-file deletion is only a workaround for handing the thread over from one client to another. It does not allow the intended Remote Control workflow of keeping a CLI thread running while viewing/steering that same live thread from the app.

    That distinction is important for severity: for anyone using Remote Control specifically to control active CLI sessions while away from their machine, the feature is currently effectively unusable.

    It also suggests this isn't simply a stale-lock cleanup issue. The current single-writer behavior seems fundamentally incompatible with simultaneous CLI + Remote Control access to the same thread, unless Remote Control is intended to attach/proxy the existing writer rather than create another one.

    I’m honestly surprised this isn’t one of the highest-priority Codex issues right now and that it has remained broken for several days. Remote Control is effectively unusable across the board for what I’d consider its most important use case: keeping an active CLI session running and then viewing or steering that same thread remotely from another device.

  12. Andingfangt commented on Aug 12, 2026

    @Andingfangt

    I can reproduce a related version of this issue on Linux with Codex CLI 0.147.0.

    My setup:

    • Linux
    • Codex CLI 0.147.0
    • codex remote-control running as a long-lived app-server under a systemd user service
    • ChatGPT mobile connected to that Remote Control host
    thread/resume failed: thread <id> already has an active writer (code -32600)
    

    As a temporary workaround, I connected the CLI TUI to the same app-server process already used by Remote Control, instead of starting a second app-server:

    codex --remote "unix:///tmp/codex-rc-XXXXXX/rc.sock"

    Resuming can also be done through that shared endpoint:

    codex --remote "unix:///tmp/codex-rc-XXXXXX/rc.sock" resume

    The socket path is generated dynamically by foreground codex remote-control, so I use a small wrapper to discover the live socket owned by the systemd service before launching the CLI.

    With both ChatGPT mobile and the CLI connected to the same app-server process, I no longer encounter the cross-process active writer conflict in this workflow.

    This suggests that the conflict is between separate app-server owners rather than between multiple UI clients connected to one app-server. It is only a workaround: a supported shared endpoint or explicit writer handoff between Remote Control, Desktop, and CLI would still be preferable.

  13. 40 remaining items

  14. AlexXF73 commented on Sep 2, 2026

    @AlexXF73

    @AlexXF73 you need to re-read @etraut-openai's comment

    The developer’s response is helpful in that it explains the root cause and acknowledges the architectural debt, but it does not promise to address the Desktop ↔ CLI handoff anytime soon. On the contrary, the current recommendation is still to quit the application that owns the thread before resuming it elsewhere.

    As I said before, for now there is only one practical workaround: ask Codex to quit/restart the Desktop app before resuming the thread in the TUI. Annoying, but acceptable until the runtimes can share the same app-server.

  15. huyz commented on Sep 2, 2026

    @huyz
  16. etraut-openai commented on Sep 2, 2026

    @etraut-openai
    Contributor

    should the desktop app share the daemon, or is desktop sharing still part of the future “eventually” work you mentioned?

    The desktop app does not currently share the daemon. It launches its own app-server instance. I would like to eventually have the app use the daemon if present, but there are additional complications that we need to work through before that will be possible.

    We've also decided to reduce the 30-minute idle timeout for thread unload to just 1 minute. This will greatly reduce the chances of hitting the "already has an active writer" error, assuming you don't have that session open in a TUI instance.

    I've also added a configuration key so you can control the idle timeout. This was previously hard-coded to 30 minutes. It will now default to 1 min, but you will be able to override this with a value of your choice (in seconds), including a value of zero. This will be included in 0.153.0, the next feature release of the CLI.

  17. alexander-dreyzen commented on Sep 7, 2026

    @alexander-dreyzen

    Additional reproduction using only desktop apps and their built-in SSH connections (macOS-to-macOS), observed September 7, 2026.

    Environment on the affected host:

    • ChatGPT desktop / Codex app: 26.901.51231, build 8109
    • macOS 26.6.2 (25G83)
    • Desktop-bundled and PATH Codex binaries both report codex-cli 0.153.4
    • Three Macs have saved SSH connections to each other, including SSH connections back to themselves.

    Reproduction and observed behavior:

    1. Open an existing, completed task on Mac A through the saved SSH connection from Mac B. Multiple remote desktop clients can access it successfully through the shared daemon.
    2. On Mac A, click the same task in the native desktop sidebar. The log records hostId=local; thread/resume fails with code -32600 and thread <TASK_ID> already has an active writer. The UI says “This is open in another app. Close it there to continue here.”
    3. The reverse also occurs: if Mac A's native app-server owns the task first, remote SSH opens fail. Selecting another task on Mac A did not release ownership during our test.
    4. Fully quitting the native app released its task lock and allowed SSH access immediately. Reopening the native app then made its local opens fail while SSH access continued to work. Restarting therefore reversed which route was blocked rather than resolving the workflow.

    Read-only process/file diagnostics confirmed two distinct app-server processes on Mac A: the desktop-spawned local server and the persistent SSH-managed daemon. lsof showed the affected task's writer-lock and session file open in the local server before quitting; afterward, the shared daemon held the task's writer-lock. No lock files were deleted, no sessions were edited, and no daemon flags were changed.

    Earlier successful opens on Mac A itself logged hostId=remote-ssh-discovered:<Mac-A-alias>. Later failing sidebar opens logged hostId=local. The self-SSH connection and remote project remained saved. The user also observed affected tasks appearing under Recents rather than under their saved SSH project; we have not established whether that grouping change causes the route change. A second Mac logged the same conflict when an own-host task was opened through hostId=local.

    Expected: an explicit saved SSH project should let the desktop on the host use that same SSH-managed session, including when the SSH destination is the same physical machine. Alternatively, the UI should provide a supported way to select that route or release/transfer ownership. We are not requesting concurrent independent writers or bypassing the lock.

    Is there a supported way to keep an existing task's sidebar entry bound to its saved SSH host, including self-SSH, without migrating/forking the task, editing internal app state, or using undocumented startup flags? This desktop-to-desktop SSH case remains reproducible with 0.153.4.

  18. lzj960515 commented on Sep 8, 2026

    @lzj960515

    @etraut-openai Thanks for explaining the single-writer protection. Is there a tracking issue or an estimated release for Desktop sharing the app-server daemon, or a supported bidirectional handoff API?

    This blocks my Codrive workflow: Codrive orchestrates tasks through codex app-server; after a task reports that it needs a human decision and the turn completes, I open the same conversation in the Codex desktop app to answer. The desktop now rejects it with already has an active writer (-32600). Codrive is a task board, and I want to keep human conversations in Codex rather than build another chat interface.

    I am using desktop 26.901.51231 (8109), bundled runtime 0.153.4, and Codrive 0.11.5 with Codex 0.153.4. Inspecting Codrive confirms it currently retains the thread subscription. We can adapt that lifecycle, but a shorter unsubscribe timeout alone would not cover the return trip: after Desktop takes ownership, Codrive later needs to resume the same conversation for automated follow-up work.

    Also, #42320 was announced for 0.153.0, but the public rust-v0.153.4 tag still has THREAD_UNLOADING_DELAY = Duration::from_secs(30 * 60) and no thread_unload_delay_secs in its config schema. Which released version will contain that change?

    I support keeping one writer per thread; the missing piece for this workflow is a supported way to move between human interaction in Desktop and external orchestration without quitting either application. Happy to follow the appropriate tracking issue if this work has moved elsewhere.

  19. alexander-dreyzen commented on Sep 8, 2026

    @alexander-dreyzen

    Follow-up after independent code review and isolated tests: the required fix is durable routing for both existing and newly created tasks, not a per-task lock or metadata workaround.

    The installed desktop keeps a native local app-server/catalog alongside the SSH-managed one. Existing-task host preference can come from a per-client saved task/project/SSH assignment. We found those assignments present on one working Mac client but absent for the same coordinators on two affected clients, despite valid saved SSH projects. Absence does not prove that restart deleted them: the creation/discovery path may never have established them on those clients.

    Ten isolated assertions against extracted installed routing functions confirmed that a missing assignment permits local fallback, while a valid SSH assignment routes a fresh sidebar click to SSH. An explicit local route in a restored view can still take precedence. A one-task offline assignment restoration has been made only as a diagnostic canary; it is not being presented as a durable fix, and its live relaunch validation is not complete.

    A separate isolated test of the installed 0.153.4 runtime, with synthetic content, temporary state, outbound IP blocked, and zero model turns, confirmed:

    • two connections to one server can resume the same task;
    • a second server can read it but cannot resume while the first owns it;
    • unsubscribing does not immediately release the writer;
    • after the owning server exits, the other can resume successfully.

    The broader acceptance requirement is that existing and newly created SSH tasks open consistently on every client, including the host machine itself, through sidebar/Recents/search/restored views and after restart/update, without per-task assignment edits, lock deletion, history migration, or recreating teams. Either supported Desktop attachment to the shared daemon or a durable SSH routing policy could satisfy this. This also connects to the sidebar/grouping report #43607, without assuming both symptoms have the same historical cause.

    Is there a supported release or configuration planned for that complete workflow? The current single-writer protection should remain intact.

  20. lpmasser commented on Sep 10, 2026

    @lpmasser

    Chiming in with what I found on the macOS desktop side, because the 1-minute unload change only fixes half of the desktop ↔ CLI handoff in my setup.

    My setup: I drive Codex from an external app-server client (codex-acp 1.10.0 on Homebrew codex-cli 0.154.0) and I just want to open the same thread in the desktop app now and then to read it. Desktop is 26.903.61454 (build 8378), which still bundles 0.153.4. Same behaviour on 26.901 before I updated. macOS 27.0, arm64.

    The part that surprised me: the app-server unload timer (30 min in 0.153.4, 60 s default in 0.154.0) only starts once nobody is subscribed, and the desktop does not unsubscribe when you click away from a thread. I poked around in app.asar and there is an InactiveThreadUnsubscriber that only sends thread/unsubscribe for a thread it owns once that thread has been "inactive" (not the active view, no followers, not running) for 3600 * 1000 ms. So a full hour. It retries every 15 s if the call fails, and you can see it in the desktop logs as inactive_thread_unsubscribe_check_scheduled / inactive_thread_unsubscribed.

    Put together, after I switch to another thread in the desktop:

    navigate away → 60 min (desktop TTL) → thread/unsubscribe → 30 min (0.153.4 unload) → lock released
    

    Roughly an hour and a half before anything else can thread/resume that thread. I haven't timed it with a stopwatch, but it lines up with what I saw: a thread I had opened once stayed an active writer long after I moved on, then at some point showed up as notLoaded on its own. And once the desktop ships 0.154.x, thread_unload_delay_secs = 0 only removes the second half. The hour on the desktop side isn't configurable anywhere I could find.

    The only things in the desktop that unsubscribe right away are archive (it unsubscribes before unarchiving, which is why archive → unarchive hands the thread back; I've verified that works) and discarding ephemeral / side-chat conversations. codex archive <id> from the CLI doesn't help here, it fails with failed to archive session while the desktop has the thread loaded.

    On the bright side, the read-only fallback in the desktop is really nice, and it's what I've ended up relying on. If my external client keeps the thread and I open it in the desktop, I get the transcript read-only with the "This is open in another app" banner and a Retry button. Tested it today: the external client held the thread, I appended two turns from it while the desktop had the thread open, Retry pulled in the new turns each time, and after the external client exited the desktop picked the thread up the moment I switched back to it. No archiving, no restart, no touching lock files.

    What I can't do is go the other way. Once the desktop has taken the thread, thread/resume from outside fails with already has an active writer until that ~90 min window has passed.

    So, what I'd love to see:

    1. The desktop releasing a thread much sooner after it stops being displayed, or at least a setting for that inactive-thread TTL, so thread_unload_delay_secs actually decides how fast a handoff happens.
    2. A 0.154.x runtime in the desktop bundle, so the config key applies there at all.
    3. Eventually some explicit way to give a thread back: an action in the desktop, a way for another process to ask the owner to unsubscribe, or the desktop following an existing local owner the way it follows SSH remotes. That would make the archive/unarchive trick unnecessary.

    If the exact snippets from app.asar or my test logs would help, say so and I'll post them.

  21. Migueldos2 commented on Sep 12, 2026

    @Migueldos2

    Linux Desktop/iOS Remote: stale active writer prevents resuming one thread

    I can reproduce the already has an active writer conflict with current Linux Desktop and iOS Remote versions.

    Environment

    • Linux Desktop app: 26.908.40834
    • Bundled Codex app-server: 0.154.0
    • iOS client: 1.2026.244 (codex_chatgpt_ios_remote)
    • Both clients connect to the same Linux host

    Symptoms

    • One specific thread still works in the client that currently owns it.
    • The same thread cannot be opened from iOS. iOS reports that it could not reconnect to the task.
    • Linux Desktop reports: “Opened in another app. Close it there to continue.”
    • Other threads on the same host can be opened normally.
    • The iOS request reaches the Linux host, so this is not a network or pairing failure.

    Relevant log entry

    failed to initialize thread persistence: thread-store conflict: thread <redacted> already has an active writer
    

    The conflict is logged during the iOS thread/resume request.

    Process state

    Two independent app-server processes were present on the Linux host:

    1. a persistent codex ... app-server --listen unix:// process whose parent is PID 1;
    2. the app-server bundled with Linux Desktop.

    The persistent process continues to own the affected thread while the Desktop/iOS-side app-server rejects thread/resume. Closing and reopening the affected task in the other client does not provide a supported takeover path.

    This appears to be the same underlying problem described in #37403: separate app-server instances cannot hand a thread over reliably, and the Desktop app does not share the existing daemon.

  22. Migueldos2 commented on Sep 12, 2026

    @Migueldos2

    Follow-up: I can confirm that the archive/unarchive workaround resolves the conflict in this setup. Archiving the affected thread and immediately unarchiving it released the active writer, after which the thread could be resumed from iOS.

  23. shlomi98 commented on Oct 4, 2026

    @shlomi98

    I can reproduce a closely related active-writer/session-ownership problem with the Codex VS Code extension on Windows.

    Steps:

    1. Start a Codex task in VS Code.
    2. Keep that VS Code task/session open.
    3. Open the same task from ChatGPT on iOS via Remote Control.
    4. The iOS app reports:

    "Another Codex session is using this task.
    Finish and close that session, then try again."

    This has persisted for several months and through multiple Codex VS Code extension updates.

    The key problem is that Remote Control cannot take over/attach while the VS Code session still owns the task. Closing the desktop session first works around it, but that defeats the intended remote-control workflow.

    Environment:

    • Windows
    • VS Code
    • Codex VS Code extension
    • ChatGPT iOS app
  24. 1Const1 commented on Oct 7, 2026

    @1Const1

    So why one merged commit activity ruined all flow and nobody can fix it?

    Just a reminder on competitor claude code remote working correctly display all messages during work and allowed to add messages to queue during task running

    This merge single lock deserved to be unrolled and removed form codex future releases

  25. hosannasub commented on Oct 10, 2026

    @hosannasub

    Adding a fresh Linux + iOS Remote reproduction on Codex CLI/app-server 0.160.0.
    This is still reproducible well beyond the original 0.147.x regression.
    In this occurrence, an older app-server remained alive and continued owning the top-level thread plus multiple child/subagent writer locks. I measured rollout growth directly over a 15-second window:

    • parent thread: +110,891 bytes
    • active child/subagent: +297,768 bytes
    • several other threads on the same app-server held writer locks but had zero rollout growth
      Inspecting session_meta confirmed the actively-writing child had both parent_thread_id and forked_from_id pointing to the affected top-level thread, and thread_source: "subagent".
      This is therefore not just a stale lock-file case. The old app-server can retain a live execution tree, including delegated subagents, while another client/session cannot safely take over the thread.
      Recovery required terminating the specific app-server that owned that execution tree. SIGTERM did not stop it, so I had to use SIGKILL. After that:
    • the parent writer lock was released
    • the parent rollout stopped changing
    • the final JSONL record remained valid
    • I backed up the rollout and successfully forked from the latest persisted parent state
    • the old parent remained released after the new fork was created
      This seems consistent with the broader ownership/handoff problem described here, but with an additional detail: ownership can cover a live parent + subagent tree, not only a single idle thread.
      My expected behavior is still that Remote Control / another client should attach to or hand off the existing execution owner rather than leave a second client blocked behind a separate app-server writer.
      Environment for this reproduction:
      codex-cli / app-server 0.160.0
      Linux remote host, accessed through ChatGPT iOS Remote / SSH workflow.
  26. TenmonAI commented on Oct 13, 2026

    @TenmonAI

    Additional sanitized report (2026-10-13): two existing macOS Codex threads cannot receive their authorized follow-up through the desktop app's send_message_to_thread tool because it returns already has an active writer (previous thread/resume error code -32600).

    The existing Remote Control app-server daemon was observed as version 0.162.1. Its process held both session rollout files open. The UI/thread readback reported notLoaded with the latest turn completed. We explicitly do NOT treat those observations as evidence that the writer is stale, that the lease has expired, or that no task is active. The underlying cause, current writer owner, active task, lease expiry and heartbeat remain unknown.

    The exposed official desktop tools and inspected app-server protocol schema did not provide an identifiable read-only active-writer lease-status query. We have not killed/restarted the daemon, removed locks, mutated the control socket, interrupted a handoff, spawned another writer, or repeatedly retried rejected sends. Existing source work is preserved.

    Could maintainers identify the supported, non-disruptive management path to:

    1. Read the current writer owner, active task, lease expiry and heartbeat for an existing thread;
    2. Deliver a follow-up to that same writer and obtain its acknowledgement;
    3. If formally proven stale, let an authorized session administrator perform an audited, narrowly scoped handoff without disrupting other active threads?

    This is a management-capability/support request, not a claim that the specific Remote Control regression in this issue has been reproduced. Project names, exact thread IDs, usernames, private paths, conversation transcripts, customer data, credentials and raw logs are intentionally omitted. Please advise an appropriate private channel if exact thread identifiers are necessary.

  27. TenmonAI commented on Oct 13, 2026

    @TenmonAI

    Follow-up to existing Codex case #37403, comment 6135733190. Please attach this report to that existing case; do not open a duplicate case.

    On macOS, an existing local thread cannot receive an authorized follow-up because it reports “already has an active writer”. The user normally exited the original Codex CLI and closed its Terminal window. After a conservative idle interval exceeding 30 minutes, a read-only check at 2026-10-13 18:09 JST still found the shared app-server holding both that thread’s rollout descriptor and writer-lock descriptor. The desktop reports notLoaded; we do not treat that as proof of ownership release.

    Recorded environment: desktop app 26.1007.30912; managed app-server 0.162.1. The official codex doctor --json command completed with exit 0. It reported the background server running but did not expose per-thread subscribers, last activity, owning connection, or a release remedy. This report does not establish that the writer is stale or that the documented idle-GC preconditions were met.

    Please inspect the affected thread’s actual subscribers, owning connection, last activity, and idle-GC state. If its original connection is still legitimately available, advise the supported unsubscribe operation on that connection. If the original connection is gone, provide a supported, audited operation that releases only the affected thread without interrupting other threads on the shared daemon, and specify its impact and how release can be verified.

    No shared-daemon stop/restart, process signal, lock deletion, raw-socket mutation, alternate writer, or repeated rejected delivery has been attempted for this report. The affected thread identifier can be supplied through an approved private support field if required. No transcript, customer data, credentials, authentication URLs, usernames, or private filesystem paths are included.

    Existing case: #37403 (comment)

    Please return a feedback/session reference and the supported management path. Receipt of this report alone will not be treated as writer release.

  28. ciiiii commented on Oct 16, 2026

    @ciiiii

    I can reproduce this problem with Codex CLI 0.162.1 on Linux (NixOS) and ChatGPT iOS Remote 1.2026.266.

    The Remote Control app-server runs as a systemd user service. The CLI and service use the same Codex installation.

    Observed behavior:

    • After closing the remote interface, the app-server continued to hold the thread writer lock, although its current task had finished.
    • The CLI could not resume that thread and displayed This conversation is open in another app.
    • Stopping the service released the lock and restored CLI access.
    • After starting the service again, iOS could not resume the same thread while the CLI held its writer lock.
    • Server logs confirmed that iOS requests reached thread/resume and failed with already has an active writer. The remote transport itself was connected.

    This is an unreasonable requirement for normal device switching. Users should not need to stop a background service or terminate a CLI session just to continue an idle task elsewhere.

    Preventing concurrent writes makes sense. Retaining exclusive ownership throughout an idle session, without a working handoff, breaks the Remote Control workflow.

    Please support both clients through the same running session, or provide reliable ownership transfer after a turn finishes. An active turn must remain protected, including tool execution.

    An earlier comment reports successful simultaneous viewing on 0.149.1. This problem still reproduces in my 0.162.1 setup. I cannot identify the first affected version.

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 workingremote

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions