Skip to content

No thread works both in the panel and via CLI at the same time (writer-lock vs. panel-visibility conflict) #45406

Open

Description

@mrehan37

What version of the IDE extension are you using?

26.908.31748 (auto-updated today from 26.901.22334 -> 26.908.40401 -> 26.908.31748)

What subscription do you have?

Plus

Which IDE are you using?

VS Code 1.132.0

What platform is your computer?

Linux 7.0.0-31-generic x86_64

What issue are you seeing?

When a thread is open in the panel, it holds a live OS-level write lock (confirmed with lsof / flock -n), which correctly blocks codex exec resume <id> from the CLI with:

thread already has an active writer (code -32600)

This makes sense as protection against concurrent writers. But there's no supported way to hand off or release that lock intentionally - e.g. to let a CLI automation drive a thread the user was previously working on in the panel, without corrupting it. Is there a planned mechanism for this (explicit release/handoff), or is "one thread = one interface at a time, permanently" the intended model?

What steps can reproduce the bug?

  1. Open an existing Codex thread in the VS Code panel (Chats list) - leave it open.
  2. From a terminal, use the extension's own bundled CLI binary
    (.../openai.chatgpt-/bin/linux-x86_64/codex) to run:
    codex exec resume "any prompt"
  3. Observe the command fails with:
    thread already has an active writer (code -32600)
  4. Confirm at the OS level this is a genuine active lock, not a stale one:
    • ls -la ~/.codex/thread-writer-locks/.lock exists
    • lsof shows the extension's app-server process holding
      an open FD on it with the write-lock flag (e.g. "43uW")
    • flock -n -c 'echo got it' fails to acquire it
  5. There is no command/menu option in the panel to release, share, or hand
    off that lock to let a CLI process drive the same thread.

What is the expected behavior?

Either:
(a) the panel exposes some explicit way to release/hand off a thread's
write lock so a CLI process (e.g. an automation or a second tool) can
take over driving it intentionally, without corrupting history, or
(b) if "one thread = one writer for its entire open lifetime" is the
intended permanent model, documentation/error messaging should say so
explicitly, so it's clear this isn't a bug or a stuck/stale lock -
right now the error message alone doesn't distinguish "temporarily
busy, retry" from "permanently locked until you close the panel."

Additional information

Extension auto-updated during this investigation: 26.901.22334 -> 26.908.40401 -> 26.908.31748, all within the same day. Behavior was consistent across all three versions - this does not appear to be a regression tied to a specific build.

codex-cli 0.154.0-alpha.6.2, VS Code 1.132.0, Linux 7.0.0-31-generic x86_64.

Activity

  1. added
    enhancementNew feature or request
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    execIssues related to the `codex exec` subcommand
    on Sep 14, 2026
  2. github-actions commented on Sep 14, 2026

    @github-actions
    Contributor

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

    Powered by Codex Action

  3. abruption commented on Sep 15, 2026

    @abruption

    Proposal: owner-cooperative release and handoff for live thread writers

    I investigated this together with the related Remote Control case in #44449 and
    several real lock holders on macOS. The important distinction is that these are
    usually live kernel locks held by a legitimate Codex process, not stale lock-file
    residue. Deleting <thread-id>.lock cannot release such a lock and can make the
    state harder to reason about.

    Current main already has most of the safe owner-side lifecycle machinery:

    • WriterLockCoordinator uses a real nonblocking OS lock and removes only
      unlocked stale files.
    • thread/unsubscribe removes a connection's subscription.
    • an unsubscribed, inactive thread is flushed, shut down, removed, and emits
      thread/closed after thread_unload_delay_secs (60 seconds by default, zero
      for immediate unloading).

    What appears to be missing is an explicit, observable handoff contract between
    products/processes. I suggest building that contract around cooperative release
    by the process that owns the writer, rather than allowing another process to
    delete files or kill the owner.

    1. Owner-side release operation

    Either add an optional immediate mode to thread/unsubscribe, or introduce a
    small thread/release operation. A possible request is:

    {
      "threadId": "...",
      "mode": "ifIdle"
    }

    The response should be structured rather than inferred from an error string:

    released | busy | subscribersRemain | notLoaded | notOwner
    

    ifIdle should release only when the owning app-server can prove that:

    • there is no active turn, delegated work, pending tool call, or elicitation;
    • the requesting connection is unsubscribed and no other subscriber still
      requires the loaded runtime;
    • pending rollout items have been flushed and the recorder shuts down cleanly.

    Only then should it remove the runtime and drop WriterLockGuard. The existing
    thread/closed notification can be the acknowledgement that ownership was
    actually released. A contender should retry thread/resume only after that
    acknowledgement.

    2. Product lifecycle integration

    • The panel should expose an explicit Release / Hand off action for an idle
      thread.
    • Remote Control clients should unsubscribe when the view is closed, backgrounded,
      or disconnected, and viewing history alone should not keep unrelated threads
      resumed indefinitely.
    • Reconnection should restore subscriptions only for threads the client is
      actually observing or running, rather than every recently viewed thread.

    This directly addresses #44449 without weakening single-writer protection.

    3. Structured conflict diagnostics

    When thread/resume encounters a live writer, its JSON-RPC error data could
    include non-sensitive fields such as:

    {
      "threadId": "...",
      "reason": "liveWriter",
      "ownerKind": "cli | appServer | remoteControl | unknown",
      "retryable": true,
      "suggestedAction": "releaseInOwner"
    }

    PID, command line, TTY, and cwd are useful for an opt-in local diagnostic command,
    but probably should not be exposed in normal protocol errors. Lock-file presence
    alone must never be reported as proof of a live owner; the OS lock probe and
    process metadata are separate evidence.

    4. Suggested regression coverage

    1. An active turn refuses ifIdle release and keeps its writer lock.
    2. An idle final subscriber can release; the rollout is flushed and unchanged
      after shutdown.
    3. Other subscribers prevent release.
    4. Remote disconnect/backgrounding releases idle threads instead of retaining
      every previously viewed thread.
    5. A second app-server initially receives a conflict, waits for thread/closed,
      then resumes successfully.
    6. Crash residue is removed only when a real nonblocking lock probe succeeds.

    An incremental first step could be the owner-side ifIdle release plus structured
    conflict data; panel and Remote Control affordances could then use the same
    protocol without changing the lock's safety model.

  4. peixotorms commented on Sep 22, 2026

    @peixotorms

    This is an example of something unnecessary. It should just takeover and thats it.

  5. HenryFone commented on Oct 1, 2026

    @HenryFone

    Windows data point that may be useful for the release/handoff discussion here (Desktop 26.924.22138, CLI 0.158.0-alpha.2.1):

    • The desktop app holds %USERPROFILE%\.codex\thread-writer-locks\<thread-id>.lock for every thread it has loaded, and keeps holding it long after the thread is idle in the UI. A second process gets the -32600 active-writer conflict, exactly as reported.
    • The lock file is opened with FILE_SHARE_DELETE, so a non-owner can rename the canonical name away while the owner keeps its handle. After renaming, an archive of that idle thread through the bundled CLI succeeded immediately (verified exit 0, DB and archived_sessions consistent, reversible via unarchive).
    • I am deliberately not proposing rename-based takeover for resume/handoff: for a thread that might actually be written, only the owner can know whether it is idle, and letting a second writer in would defeat the lock. I only used the rename as a stopgap for the archive path on provably idle threads.

    That is why I think the owner-side ifIdle release plus structured conflict diagnostics you outlined is the right shape: a contender's lock probe cannot distinguish "live owner, idle thread" from "live owner, active turn", and today no supported request lets an owner drop the lock on demand.

    For anyone stuck on the archive path meanwhile, I put the rename + official CLI (archive/unarchive/delete) flow in a small stdlib-only local tool: https://github.com/HenryFone/codex-archive-manager (Windows 11 + 26.924 tested; 127.0.0.1 only; never edits the SQLite DB itself). Untested on Linux/macOS, and it does not implement resume or handoff.

  6. abreza commented on Oct 8, 2026

    @abreza

    Please add a read-only “Follow CLI” mode to the VS Code extension, showing live progress, tool output, and diffs from a CLI session running in background or tmux. The CLI should remain the sole writer, and closing the extension should not interrupt its work.

  7. mrehan37 commented on Oct 12, 2026

    @mrehan37
    Author

    Queue helps,

    i used claude to give codex the prompt/instructions so the lock thing effect me and thats why i made this thread but the same day i got the solution that queue can help, even for nomal message when the chat is not doing anything you just use the queue function cuz without queue you cant directly add anything to the chat, so tell your ai or update the scripts of yours to QUEUE everything it wanna send to the codex, by this way you can quue multiple commands

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

    enhancementNew feature or requestexecIssues related to the `codex exec` subcommandextensionIssues related to the VS Code extensionsessionIssues involving session (thread) management, resuming, forking, naming, archiving

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions