Skip to content

Document how app-server clients can discover and attach to the active Codex Desktop thread #25914

Open

Description

@bakemocho

What feature would you like to see?

I would like a supported, documented app-server path for a local external client
to discover and attach to the currently active Codex Desktop thread/turn.

The goal is not to change model behavior. The goal is to let a local integration
safely answer:

  • which thread is the active Desktop UI session?
  • is there an in-flight turn?
  • can the client subscribe to that thread's live events?
  • can the client steer or interrupt that in-flight turn through a supported API?
  • if this is unsupported, how should clients detect that instead of guessing?

Problem

The documented app-server primitives work well for threads that the client
creates through app-server: a client can start a thread, start a turn, observe
events, and call turn/interrupt against a turn it owns.

The unclear case is an already-running Codex Desktop UI session. In local
experiments, an external client could see stored thread history, but could not
reliably bind that persisted thread to a loaded, controllable Desktop UI turn.

Observed shape:

  • thread/list can show stored threads.
  • thread/read with includeTurns:false can read a selected stored thread.
  • thread/loaded/list can return zero loaded threads from the client-visible
    app-server surface.
  • a turn/interrupt attempt against the selected active-thread candidate
    returned thread not found.
  • direct stdio against the active Desktop-bundled codex app-server can return
    inventory, but still did not expose a loaded UI thread to control.

That leaves external clients with unsafe options: infer the current thread from
local session files, update times, or UI-visible labels, then try a control
method that may be targeting a persisted-but-not-loaded thread or the wrong
app-server process.

Environment from one local probe

  • Platform: macOS / Darwin arm64
  • Codex Desktop bundle: 26.527.60818 / build 3437
  • Desktop-bundled codex: codex-cli 0.136.0-alpha.2
  • Standalone current codex: codex-cli 0.135.0

Why this matters

This blocks safe local orchestration around Codex Desktop sessions.

Examples:

  • a local supervisor wants to notify or steer a long-running Desktop session;
  • a recovery tool wants to tell whether a Desktop turn is still running, lost,
    interrupted, or only persisted;
  • a multi-agent/local automation layer wants to coordinate with a user-visible
    Desktop session instead of creating detached hidden workers;
  • a client wants to avoid scraping local session files or guessing from file
    modification times.

Without an official current-Desktop-thread discovery/attachment contract, a
client can prove that app-server control works for threads it created, but not
that it can safely control the existing Desktop UI thread the user is looking at.

Desired outcome

Any of these would address the gap:

  1. A documented API to read the current Desktop UI thread id and active turn id
    for the local app instance.
  2. A documented way to attach/subscribe an external app-server client to an
    already-loaded Desktop UI thread.
  3. A documented way to resume or load a persisted thread into a controllable
    app-server process without starting a new unrelated UI session.
  4. Clear errors that distinguish:
    • unknown thread id,
    • persisted but not loaded,
    • loaded in another app-server process,
    • no active turn,
    • unsupported existing-UI-session control.
  5. Documentation stating that existing Desktop UI session control is not
    supported, plus the recommended integration pattern.

Related issues

Related but not the same:

Additional notes

I am intentionally not including raw thread ids, turn ids, local session file
paths, or transcript text here. The public observation is the control-surface
shape: persisted thread visibility does not currently imply that an external
client has a supported loaded-turn target for the current Desktop UI session.

Activity

  1. added
    documentationImprovements or additions to documentation
    appIssues related to the Codex desktop app
    app-serverIssues involving app server protocol or interfaces
    on Jun 2, 2026
  2. bakemocho commented on Jun 2, 2026

    @bakemocho
    Author

    Update: After filing this, I found the 2026-05-29 Codex app 26.527 changelog
    and the app docs for thread coordination/worktrees. I had not seen that feature
    when I opened this issue; if I had, I may not have filed it in this exact form.

    The remaining gap I meant to ask about is narrower: I still do not see a
    documented app-server / external-client contract for discovering or attaching to
    the thread/turn that is currently active in the Codex Desktop UI. So I think
    this issue is better scoped as a request to document that boundary, not as a
    request for general thread/worktree coordination.

    One practical caveat: I am close to my current Codex quota limit, so I cannot
    immediately verify the new Desktop thread-coordination behavior myself. For now
    I am effectively blocked from testing the workflow end to end until quota is
    available again.

  3. bakemocho commented on Jun 4, 2026

    @bakemocho
    Author

    I have a more precise update after testing the app-server control path again.

    The large-history part is no longer the main blocker in my local repro. With a
    detached local app-server process, I can resume the current Desktop thread in a
    low-payload shape and verify that the bounded first page
    matches the active-turn candidate I found locally. That part did not hit the
    2 MB reader limit.

    The remaining gap is live-turn ownership or attachment:

    • In the detached app-server process, the current Desktop thread can be loaded
      and resumed, but turn/steer for the discovered active turn returns
      no active turn to steer.
    • Talking directly to the daemon control socket over WebSocket also works for
      initialize, thread/loaded/list, and thread/resume with excludeTurns.
      The selected Desktop thread is visible as loaded there too, but the same
      turn/steer request still returns no active turn to steer.
    • Separately, codex app-server proxy --sock ... still times out for a minimal
      WebSocket-framed initialize, while the raw socket route succeeds. I think
      that is a different proxy/transport issue from the active-turn attachment
      question here.

    So the request I would find most useful is documentation for this boundary:

    Can an external local app-server client attach to the live active turn that is
    currently running in the Codex Desktop UI, or can it only load/read/resume the
    persisted thread record in its own control context? If the latter is intended,
    an explicit unsupported-boundary note and the supported alternative would be
    enough.

  4. hhy827 commented on Jul 2, 2026

    @hhy827

    Author's note: I'm a Korean speaker; I directed this and made the calls, and the English was written by the AI I operate (Claude and Codex).

    First, I'm writing to support this request — and to add one real-world data point from actual business use.

    I'm not a software developer. I run a small renewable-energy company and handle real business work (permitting, economics, regulatory review) with AI. My tools are the GUI desktop apps, and the one relevant to this issue is the Codex app. The hypothetical examples you list — "a local supervisor that wants to notify or steer a long-running Desktop session," and "a local automation layer that wants to coordinate with a user-visible Desktop session instead of creating detached hidden workers" — I'm a real-world instance of both, so I'm here to add the weight of an actual need, not a hypothetical one, to your request.

    Let me be honest about how I stumbled into this. At first I was the wire between several AI sessions: I copied one session's answer, pasted it into another's window, and carried the thread back by hand. Then I moved that exchange onto a shared channel (Slack) so they could see each other — but I still had to tell each session, by hand, to "go check the channel." Then I moved the work itself into a queue (Linear), so what/who/how-far lived in the queue instead of my head. The last manual thing left was exactly that "go check the channel."

    I was only trying to remove that one chore — I was not trying to invent "waking a session." I just wanted the manual relaying gone. But while wiring that automation, I found that, in my setup, when the signal reaches the intended already-running session, it doesn't start fresh — it resumes with its context intact. It was a by-product of removing a chore, not a goal. And that by-product turned out to be exactly what lets multi-day business work continue without re-briefing it every morning.

    Here's how I actually use it (shape only): a work signal in Slack/Linear → a durable routing queue → delivery targeted to a specific seat → the already-running desktop session picks up with its context intact. Waking the existing session, not spawning a new one. Across several of my own machines, at real business scale, over many days. For multi-day work, that context continuity is the whole point.

    Consistent with your findings, I do not rely on a documented app-server attach path — because, as you show, there isn't one for the live Desktop UI turn. What I have is an unofficial local integration I prop up by hand, and it's fragile in exactly the ways that follow from there being no supported contract: the monitoring around a session can fail, sessions drop on app update/restart, and an idle session won't wake without an external trigger.

    For that reason I deliberately do not publish the exact path (no thread ids, no session file paths, no method) — the same discipline you kept. I'm not withholding to be secretive; I'm withholding because an unsupported workaround is precisely the thing that shouldn't be relied on. That is the very reason a supported contract, which this issue asks for, is needed.

    On legitimacy: official apps + my own paid subscription + my own machines only. I'm not trying to bypass usage limits or billing — routing the signal is the same instruction I would otherwise type into the session by hand. If OpenAI says this pattern is unsupported or inappropriate, I'll stop and use whatever supported path is available.

    So here is my request. This need is already load-bearing in a real business. Please make this an official, documented capability — the things that map directly onto your desired outcomes:

    • a documented way to discover/attach to the active Desktop UI thread/turn;
    • provenance — external-channel input visibly distinguished from local user input;
    • approval preservation — delivery must not equal execution permission.

    My hope is clear: not to be prevented from doing this, but to be able to do it safely. More non-developer domain operators will end up here. Please build this one supported, documented, governable boundary — so none of us has to reinvent the fragile wiring.

  5. AstraVanX commented on Aug 1, 2026

    @AstraVanX

    I independently tested this gap on native Windows using a one-shot, strictly read-only capability preflight.

    Environment:

    • Codex Desktop AppX version: 26.727.6591.0
    • Platform: native Windows 11 x64
    • The Desktop-bundled codex.exe was identified by file size, Authenticode/AppX identity, and SHA-256.
    • The Desktop child process was observed running app-server --analytics-default-enabled.

    Observed result:

    • The Desktop-launched app-server exposed no TCP listener or documented local control endpoint that an external client could safely associate with the current Desktop UI instance.
    • Because that association could not be proven, the
  6. TheAngryPit commented on Sep 1, 2026

    @TheAngryPit

    Concrete downstream use case from OpenClaw's official Codex integration:

    OpenClaw can currently discover and read persisted Codex Desktop threads and can safely create a separate supervised continuation from terminal history. It deliberately does not claim control of a live Desktop-owned turn, because App Server ownership/status is process-local and a second App Server cannot safely act as the existing UI turn's owner.

    While investigating a possible native integration, we found the same-user IPC router used by Codex Desktop and the VS Code extension. The public Codex source exposes the socket framing and ide-context client path, but the thread-follower control methods used by the app surfaces are not currently a documented public contract. We therefore do not want to ship an OpenClaw adapter that depends on private method names or reverse-engineered Desktop behavior.

    Could maintainers clarify whether that owner/follower IPC path is intended to become a supported external-client contract? The minimum useful contract for this downstream would be:

    • discover the current owner for a known thread and its active turn;
    • start a turn on an idle owner, or steer/interrupt an active turn;
    • subscribe to lifecycle/output events without creating a competing writer;
    • same-user socket and ownership validation;
    • protocol/version negotiation and explicit no owner, busy, and unsupported results;
    • visible provenance for externally delivered input, while preserving normal approval boundaries.

    If such a contract is supported, we can implement a small opt-in OpenClaw adapter and retain the current persisted-thread branch/resume behavior as the fallback when no live owner is available. We can also provide macOS and Windows integration testing against the supported shape.

    This is not a request to bypass Codex permissions or account limits. Delivery should remain distinct from execution approval; the goal is simply to coordinate the same user-owned task that is already visible in Codex Desktop rather than create a detached duplicate.

  7. barneysspeedshop commented on Sep 27, 2026

    @barneysspeedshop

    Adding the complementary external-client case from Rope Notes.

    Rope Notes can launch its own codex app-server and own the threads it creates. Users will also want to open a task that is already running in Codex Desktop, inspect it in Rope Notes, and steer it or answer an input request without creating a detached continuation or a competing writer.

    We would treat this as an explicit attachment to the existing owner. The minimum contract we would need is:

    • identify the exact Desktop instance that owns a thread and its active turn;
    • subscribe to that turn with a replay cursor so a reconnect can fill any event gap;
    • route steer, interrupt, approval, and input responses to the owning process;
    • return distinct results for no owner, persisted but not loaded, idle, busy, and unsupported;
    • mark externally supplied input with provenance while preserving the existing permission and approval boundaries.

    Without that contract, Rope Notes can safely support only the client-owned shape. It would have to present an already-running Desktop task as read-only or create a separate continuation, which splits the user experience and can duplicate work.

    We would be glad to test a supported attachment design on macOS and Linux from the same Dart client used for the client-owned mode.

  8. mxroach commented on Oct 12, 2026

    @mxroach

    Windows Desktop native stdio execution lacks exposed target-only cancellation path through managed app-server proxy

    Component: Windows desktop / app-server / app tool integration. Desktop package version: 26.1007.2314.0; bundled native CLI: 0.162.0-alpha.17.2.

    Goal: normally cancel one existing desktop-owned chat without interrupting other chats, restarting the app/server, or moving the checkout.

    Read-only reproduction:

    1. On the existing Windows desktop host, inspect public Win32_Process app/server parent metadata. The app-associated server uses default stdio; a separate managed server uses unix://. Do not terminate either server.
    2. Connect the already running documented codex app-server proxy; perform its WebSocket and initialize handshake. Read thread/read for the selected existing thread with includeTurns=false, and exhaust thread/loaded/list pagination.
    3. The proxy successfully reads the selected persisted thread as notLoaded, but the selected thread is absent from the complete loaded list. The supported application read shows a persisted interrupted turn with completedAt=null; its item history grows across successive reads. These observations do not prove that the native execution stopped or is absent on every server.
    4. Current app tools provide exact thread reads and messages, but no target-only turn cancellation. A previously attempted supported message was rejected because the thread had an active writer; this is historical evidence, not a fresh submission. The handoff operation also moves Git/chat state and is not an equivalent stop-only operation.

    Expected: a documented bridge/tool that binds an existing native owning server and fresh active turn, issues only that turn's normal cancellation, and provides an authoritative completion/release event. No global restart, force kill, private IPC injection, resume/takeover, or new thread should be required.

    Question for maintainers: which supported integration controls the existing native stdio desktop owner from another authorized chat/device? If none is exposed, can the app provide exact-thread cancellation with fresh turn identity and release verification?

    Limits: no interrupt/contact request was sent during this reproduction. Current native owner/turn binding remains unverified. This is an observed integration capability gap, not a source-level product defect or verified cancellation. A separate task-local diagnostic overclaim was fixed and covered by 12 synthetic tests; those tests are not native cancellation proof.

    Official references:
    https://learn.chatgpt.com/docs/app-server
    https://learn.chatgpt.com/docs/remote-connections

  9. mxroach commented on Oct 13, 2026

    @mxroach

    Follow-up: response-only delivery also hits the existing-writer boundary

    Additional observation on the same Windows Desktop integration, separate from the cancellation request above. Desktop AppX: 26.1007.2314.0; bundled app-tools plugin: 0.1.6.

    The task was only to deliver a scoped question to an existing chat and read its answer. It did not authorize starting another implementation, interrupting the existing writer, changing its model/settings, or creating a replacement chat.

    Observed through supported app tools:

    • One call to send_message_to_thread for existing remote chat A, whose destination host was verified in the coordination record, returned thread <TARGET_A> already has an active writer.
    • A separately approved, single relay request to another existing chat B returned thread <TARGET_B> already has an active writer. This call omitted the optional destination host argument; omission did not establish a working route from this caller.
    • Supported read_thread calls still read both exact existing chats. Returned stored statuses such as notLoaded or interrupted did not mean unreadable or establish that their writers had stopped.
    • Post-error reads found no exact request body in the inspected public input windows. This is not a claim of whole-history or private-queue absence, and neither delivery nor owner agreement was inferred.
    • Earlier same-host coordinator sends had completed with corresponding public destination inputs visible. That route contrast does not prove these later requests succeeded.

    Static inspection of the installed MCP bridge shows that its normal tool-call handler passes the supplied arguments to the app and maps the app's result into MCP content and isError. That file contains no tool-specific sender, thread-resume or active-turn steering implementation. This does not establish the app handler's full internal call stack or prove which component must change.

    No failed request was blindly retried. No writer was stopped, resumed, replaced or reconfigured; no lock was deleted; no private control-channel injection or new listener was used.

    Maintainer question: what supported app-tool path routes a response-only message to the existing native owning writer, including when the caller is another authorized chat/device? If that ownership route is not currently exposed, could the tool return an explicit capability/ownership result instead of requiring a competing writer or a new chat?

    Please preserve source provenance and execution approval boundaries: receipt of a coordination question must not itself authorize implementation, deployment or financial effects.

    Official protocol reference: https://learn.chatgpt.com/docs/app-server


    Additional sanitized evidence: resume preflight precedes message dispatch

    Inspected on 2026-10-14 (Asia/Seoul), Windows Desktop distribution 26.1007.2314.0. This is distributed-bundle evidence, not a public source-commit identity or an end-to-end fix.

    A narrowly scoped, read-only inspection beyond the generic MCP bridge identifies this app-specific path:

    • webview/assets/app-initial-b8e1c8014bb4.js: joc resolves the target host and calls Goa; Goa calls the host manager's sendFollowUpMessage and propagates failure.
    • webview/assets/app-shared-737655e1fb23.js: sendFollowUpMessage delegates to Pln. It awaits resumeConversation first. A rejected resume, or its not-ready result, prevents reaching turnCoordinator.sendPreparedMessage.
    • Wan has a ready path for an already attached non-cloud follower. Otherwise Han requests thread/resume; its retries cover closing/timeouts, not active-writer conflicts. The active-writer catch can hydrate stored history but rethrows the error instead of dispatching or queueing the follow-up.

    Rechecked distribution pins: shared bundle SHA256 a1daa891e89a395b8db399ff6eaa365f12a86880c3d8cf891caf4ccbcb9eebf5; extracted Pln SHA256 bd0c2da8d1e37461ceb10b7606a0025306e922b1f9906ed8d27e0fcc79b4351b. The corresponding source commit is unavailable and is not inferred.

    Only the exact extracted function was executed in an isolated JavaScript VM with a synthetic manager, without evaluating the bundle or making real app-server/network/private-control calls:

    Synthetic resume result Resume calls Prepared dispatches
    Reject: already has an active writer 1 0
    Not-ready: owner-recovering 1 0
    Ready: positive control 1 1

    All three assertions passed. This proves the conditional pre-dispatch barrier, not the original failures' full runtime stacks, owning-process identity, absence from a private queue, or successful delivery/recovery. The earlier direct-host wording was narrowed to the verified target metadata; literal original call arguments were not independently recovered in this later inspection. A host override alone is not established as a remedy.

    The schema exposed to this caller has threadId, optional hostId, prompt, optional model and thinking, but no queue mode, expectedTurnId, or separate writer-attachment operation. This concerns this exposed tool surface, not every possible client/transport. The documented steer protocol requires a matching active turn ID. A stored-history read does not prove owning-writer attachment or message receipt.

    Requested improvement: provide or document a supported attach/forward path to the existing owning writer before dispatch, with an explicit queue/steer choice where supported. Preserve the single-writer guard, authorization/provenance, and ambiguous-outcome non-retry rule. If attachment is unsupported, return an explicit capability/ownership and delivery-stage result. Transport acceptance, actual destination input, and a substantive reply should remain distinct receipts.

    This edit adds evidence only. No new target message was sent, writer/lock/package/settings change was made, or automatic reply/owner consent verified.

  10. mxroach commented on Oct 18, 2026

    @mxroach

    New Windows evidence: the target writer resource belongs to the Desktop child, while the read-only observer reports notLoaded

    Follow-up to the response-only delivery report above. Observation date: 2026-10-18 UTC; native Windows; installed bundled CLI 0.162.0-alpha.17.2. This reports observations, not a recovery or a new target-message attempt.

    What is newly established

    • The documented Windows Restart Manager resource query (RmRegisterResources / RmGetList) returned exactly one process using the selected thread's writer-lock file. Its PID and creation time matched independent Win32_Process metadata: a codex.exe app-server child of the Desktop app. Raw PIDs, timestamps, host/user names, thread IDs and resource paths are deliberately omitted.
    • The resource query was validated first against a task-owned local fixture: the held file identified its process; the closed file did not, both before and after. All temporary Restart Manager sessions ended normally. Registering resources uses temporary OS session metadata; no Codex setting was changed.
    • The identified app-server had no explicit --listen override and no TCP listener in the scoped process inventory. The version-matched public CLI source defaults this case to stdio://; the stdio path is single-client, separately selected from the Unix-socket/WebSocket acceptors.
    • Meanwhile, the already-existing official app-server proxy successfully read the exact stored thread as notLoaded. Complete queried, attempt-relevant persisted item pages and the complete current queue contained no exact new coordination request or explicitly correlated answer; the current queue was explicitly empty. These are persisted-observer results, not a global absence claim for every private/unpersisted buffer.

    This supports a concrete observer/owner context mismatch, rather than treating notLoaded as proof that the Desktop writer has released ownership. The process resource attribution is narrower than tracing the complete app-tool routing stack: Restart Manager reports file-resource users, not the current byte-range lock state, and no live lock-acquisition probe was performed.

    For balance, an earlier, distinct queued request was independently observed as an actual destination input and received a scoped reply. That success is not evidence that the later failed request arrived; this is not a claim that all queue consumption is broken.

    Source boundary and requested supported path

    Public source inspected at openai/codex@740e5af33c71225640e0c1c1555c514c2c93ab74:

    This is version-matched source inspection, not independent reproduction of the distributed binary's build. Also, absence of a --remote-control argument does not prove that persisted remote control is disabled.

    What supported app-tool/client operation binds a known existing Desktop-owned writer and forwards an authorized response-only question through that owner, without a competing resume, another writer, a replacement chat, or a restart? If no such attachment is exposed, please provide an explicit unsupported/ownership result and distinguish transport acceptance, destination input receipt, and substantive reply.

    No failed target request was resent, re-enqueued or resumed for this follow-up. No target process was stopped/restarted; the target lock file was not opened, read, deleted, or probed for lock acquisition. No private IPC/handle injection was used, and no new server/listener or security setting was introduced. Only short-lived official proxy read clients were used and normally closed. Receipt of a coordination question must remain separate from execution approval.

    Official App Server documentation

  11. barneysspeedshop commented on Oct 18, 2026

    @barneysspeedshop

    Update from Rope Notes

    We now use a QR-paired Remote Control connection to let an Android client continue desktop Codex conversations. The integration supports:

    • Live conversation and tool-activity observation
    • Follow-up messages, steering, and interruption
    • Question and approval responses

    The distinction that mattered was connecting to the desktop’s shared app-server, rather than launching a separate app-server and attempting to resume the same thread there.

    We select a specific chat; this does not establish a general API for discovering whichever chat is currently focused in Desktop.

    Remaining gaps

    A documented third-party attachment contract—including supported transports, ownership semantics, capability negotiation, and reconnect/event-replay behavior—would still be valuable. Our working integration should not be taken as evidence that every transport or Desktop configuration supports the same behavior.

    Prepared with AI assistance at my request.

  12. moonbird-northroot commented on Oct 19, 2026

    @moonbird-northroot

    Following the recent Rope Notes shared-server/QR-pairing update, could a maintainer clarify whether that route is supported for third-party clients on native Windows, and link the contract and minimum versions?

    Observed environment: Windows desktop distribution 26.1007.9446.0, with bundled codex-cli 0.162.0-alpha.17.2. The client would target one specific known chat.

    The initial requirement is read-only: correlate turn IDs and errors, observe retry ownership/state, and distinguish configured model/reasoning defaults from the actual inference profile. Later automation would need to coordinate with desktop-owned retries to avoid duplicate work.

    Does the supported Windows contract cover transport discovery/pairing, single-writer ownership, and reconnect/event replay? If attachment to an existing desktop-owned chat is unsupported, which integration pattern should an external client use instead? This is related to #40134, but concerns an already-running desktop-owned chat.

    This asks for interface guidance only. No logs, transcripts, chat IDs, private paths or access details accompany this comment.

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 interfacesdocumentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions