Repository navigation
Document how app-server clients can discover and attach to the active Codex Desktop thread #25914
Description
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/listcan show stored threads.thread/readwithincludeTurns:falsecan read a selected stored thread.thread/loaded/listcan return zero loaded threads from the client-visible
app-server surface.- a
turn/interruptattempt against the selected active-thread candidate
returnedthread not found. - direct stdio against the active Desktop-bundled
codex app-servercan 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/ build3437 - 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:
- A documented API to read the current Desktop UI thread id and active turn id
for the local app instance. - A documented way to attach/subscribe an external app-server client to an
already-loaded Desktop UI thread. - A documented way to resume or load a persisted thread into a controllable
app-server process without starting a new unrelated UI session. - 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.
- Documentation stating that existing Desktop UI session control is not
supported, plus the recommended integration pattern.
Related issues
Related but not the same:
- Clarify app-server turn lifecycle semantics for external clients #20943 asks for clearer app-server turn lifecycle semantics for external
clients. This issue is about identifying and attaching to the active Desktop
thread before lifecycle handling begins. - Support inbound MCP notifications routed into an active Codex CLI session #15299 asks for inbound notifications into an active CLI session. This issue
is about app-server clients and Codex Desktop. - Support remote app-server threads in Desktop thread orchestration #25482 asks for remote app-server threads in Desktop thread orchestration.
This issue is about the local active Desktop UI thread. - Codex Desktop meta-bug: unbounded session/turn state causes freezes, context bloat, and lost active-turn control #25779 is a broader Desktop/session active-turn reliability meta issue. This
issue is narrower and asks for a supported external-client contract. - TUI turn interrupt failure can lose session context #24518 covers a TUI
turn/interruptfailure path. - codex app-server proxy hangs on WebSocket-framed initialize while direct Unix socket succeeds #25846 covers
codex app-server proxytransport behavior; this request is
about active Desktop thread discovery/attachment, not proxy framing.
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
bakemocho commentedon Jun 2, 2026
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.
bakemocho commentedon Jun 4, 2026
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:
and resumed, but
turn/steerfor the discovered active turn returnsno active turn to steer.initialize,thread/loaded/list, andthread/resumewithexcludeTurns.The selected Desktop thread is visible as loaded there too, but the same
turn/steerrequest still returnsno active turn to steer.codex app-server proxy --sock ...still times out for a minimalWebSocket-framed
initialize, while the raw socket route succeeds. I thinkthat 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.
hhy827 commentedon Jul 2, 2026
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:
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.
AstraVanX commentedon Aug 1, 2026
I independently tested this gap on native Windows using a one-shot, strictly read-only capability preflight.
Environment:
26.727.6591.0codex.exewas identified by file size, Authenticode/AppX identity, and SHA-256.app-server --analytics-default-enabled.Observed result:
TheAngryPit commentedon Sep 1, 2026
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-contextclient 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:
no owner,busy, andunsupportedresults;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.
barneysspeedshop commentedon Sep 27, 2026
Adding the complementary external-client case from Rope Notes.
Rope Notes can launch its own
codex app-serverand 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:
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.
mxroach commentedon Oct 12, 2026
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:
codex app-server proxy; perform its WebSocket and initialize handshake. Readthread/readfor the selected existing thread with includeTurns=false, and exhaustthread/loaded/listpagination.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
mxroach commentedon Oct 13, 2026
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:
send_message_to_threadfor existing remote chat A, whose destination host was verified in the coordination record, returnedthread <TARGET_A> already has an active writer.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.read_threadcalls still read both exact existing chats. Returned stored statuses such asnotLoadedorinterrupteddid not mean unreadable or establish that their writers had stopped.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:jocresolves the target host and callsGoa;Goacalls the host manager'ssendFollowUpMessageand propagates failure.webview/assets/app-shared-737655e1fb23.js:sendFollowUpMessagedelegates toPln. It awaitsresumeConversationfirst. A rejected resume, or its not-ready result, prevents reachingturnCoordinator.sendPreparedMessage.Wanhas a ready path for an already attached non-cloud follower. OtherwiseHanrequeststhread/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; extractedPlnSHA256bd0c2da8d1e37461ceb10b7606a0025306e922b1f9906ed8d27e0fcc79b4351b. 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:
already has an active writerAll 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, optionalhostId,prompt, optionalmodelandthinking, 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.
mxroach commentedon Oct 18, 2026
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
RmRegisterResources/RmGetList) returned exactly one process using the selected thread's writer-lock file. Its PID and creation time matched independentWin32_Processmetadata: acodex.exeapp-server child of the Desktop app. Raw PIDs, timestamps, host/user names, thread IDs and resource paths are deliberately omitted.--listenoverride and no TCP listener in the scoped process inventory. The version-matched public CLI source defaults this case tostdio://; the stdio path is single-client, separately selected from the Unix-socket/WebSocket acceptors.app-server proxysuccessfully read the exact stored thread asnotLoaded. 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
notLoadedas 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:WriterLockCoordinator::acquire: the active-writer error followsfile.try_lock()returningWouldBlock, not merely lock-file existence.AppServerTransport::DEFAULT_LISTEN_URLandrun_main_with_transport_options: default stdio and separate transport branches.This is version-matched source inspection, not independent reproduction of the distributed binary's build. Also, absence of a
--remote-controlargument 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
barneysspeedshop commentedon Oct 18, 2026
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:
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
command/execterminals are separate from Desktop’s visible terminal tabs: Codex Desktop: expose opt-in local Terminal API for visible terminal tabs #25586.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.
moonbird-northroot commentedon Oct 19, 2026
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 bundledcodex-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.