Skip to content

Feature request: deliver external/async events as turns into a live (visible) session — today only headless remote-control threads are reachable #33556

Open

Description

@Lzzzs

Summary

Codex has no supported way for an external, asynchronous event (a file-watcher signal, a webhook, an incoming message/notification) to wake a running, user-visible session and inject a turn into it. External injection through the remote-control daemon only drives headless threads, which never surface in the desktop app window or a foreground codex TUI the user is actually watching.

The transport pieces already exist (remote-control + thread/start + turn/start) — they just can't target the session a human has open.

What I observed (why it isn't possible today)

macOS (Apple Silicon), Codex CLI 0.140.x with managed app-server 0.142.x, verified on a real machine via live process inspection + WS-over-unix RPC:

  • The visible desktop app's backing app-server runs as a private stdio subprocess with no listening socket:
    /Applications/Codex.app/Contents/Resources/codex app-server --listen stdio://
    → nothing external can connect to it.
  • The only externally reachable endpoint is a separate remote-control daemon:
    codex … --remote-control --listen unix://…/app-server-control/app-server-control.sock
  • thread/start + turn/start over WS-over-unix to that control socket does wake an idle headless thread and run a full turn (observed turn/started → streamed item/agentMessage/delta → turn/completed, real assistant output).
  • But that headless thread lives in a different process / thread-store than the desktop window (confirmed with distinct live PIDs for the window's app-server vs. the injectable daemon). The injected turn never appears in the window the user is looking at.

Net: you can wake a headless agent, but not the session you have open. There is no --listen/attach option that both (a) serves a visible interactive session and (b) is reachable by an external turn/start.

What I'm asking for

Any one of these would close the gap:

  1. An opt-in flag to expose a live interactive session (desktop app or codex TUI) on the remote-control endpoint, so an external turn/start is delivered as a new turn in that session.
  2. Let the desktop app / TUI attach to the remote-control daemon's thread-store (shared threads), so a headless-injected turn surfaces in the visible window.
  3. A first-class "external wake" primitive — the model Claude Code's Monitor tool already provides: you register a long-lived watcher process, and each line it emits on stdout becomes an asynchronous user-turn in the live session. Codex has the injection transport (turn/start) but no equivalent way to bind a watcher to the session the user is watching.

Motivation

Event-driven automation often needs to wake the session the user is actively watching when something happens outside Codex — a file changes, a webhook fires, a message arrives — and have the agent pick it up as a new turn without the user re-prompting. Today the only externally wakeable target is an invisible headless thread, so any such flow has to run where the user can't see it. A supported wake path for live sessions would remove that limitation.

Environment

  • Codex CLI 0.140.x, managed app-server 0.142.x
  • macOS (Apple Silicon)
  • Observed 2026-07 on a real machine.

Activity

  1. added
    enhancementNew feature or request
    CLIIssues related to the Codex CLI
    appIssues related to the Codex desktop app
    app-serverIssues involving app server protocol or interfaces
    on Jul 16, 2026
  2. github-actions commented on Jul 16, 2026

    @github-actions
    Contributor

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

    Powered by Codex Action

  3. ctut commented on Jul 26, 2026

    @ctut

    Adding a concrete macOS accessibility/productivity use case that matches this request.

    The user wants a bare Fn push-to-talk helper: holding Fn records locally, releasing it transcribes, and the resulting text is delivered into the same visible Codex Desktop thread they are already using. If any other key or modifier is pressed, capture is abandoned and the original key combination continues to its normal macOS/application handler unchanged.

    The capture and transcription side can be built without UI automation. The blocker is safe delivery into the live Desktop session. A separate app-server can create or resume its own control context, but that does not prove delivery into the exact in-flight session visible to the user.

    For this workflow, a supported bridge would ideally provide these properties:

    • The target thread is prebound by the Desktop-side integration rather than supplied with each request.
    • Start-versus-steer selection occurs atomically inside the app process using current live state and the exact active turn identity.
    • Each capture has one idempotency identity; duplicate and ambiguous acknowledgements never cause automatic resend.
    • The local helper is authenticated through a narrow owner-private endpoint rather than by process name or a self-declared client type.
    • External input remains ordinary user input and does not grant approvals, permissions, or execution authority.
    • No Accessibility typing, clipboard injection, synthetic Return key, private IPC reverse engineering, or second competing app-server is required.

    We have intentionally held activation of the otherwise feasible capture/transcription half because presenting it as complete without a supported live-session bridge would be misleading. An opt-in live-session attachment or fixed-target external-wake primitive would unblock this use case cleanly.

  4. PMP57 commented on Aug 30, 2026

    @PMP57

    Adding a cross-vendor agent-coordination use case and recent sandbox measurements that reinforce this request.

    For ORCH, an external hub daemon needs to wake an already-open Codex Desktop task only when an addressed message arrives. The quiet-period requirement is zero model invocations and zero model tokens. A headless watcher can launch or resume a headless Codex process, but that does not keep the existing visible Desktop task in the loop; top-of-turn polling also cannot reach a task while it remains idle.

    Comparable product mechanisms show that the requested lifecycle is not hypothetical. Claude Code channels push messages, alerts, and webhooks from MCP servers into a running session, while Claude Code plugin monitors keep a background process for the session lifetime and deliver its stdout lines as notifications. Google Antigravity sidecars are host-managed background processes that can use agentapi send-message to address an existing conversation; Antigravity subagents automatically wake from idle when messaged and retain prior context. Antigravity artifact feedback also continues the associated agent workflow. These mechanisms are not identical contracts, and the Antigravity documentation does not state the same quiet-token accounting, but they demonstrate that event-driven wake and continuation are established product patterns.

    In an isolated August 2026 app-server queue probe, we observed:

    • Idle queue/add auto-started before the client could issue queue/start in 8/8 repeats.
    • A busy explicit start was rejected without losing the queued item; the queue auto-drained after the active turn completed.
    • After interruption, the queue remained paused until an explicit start.
    • Core accepted duplicate client IDs, so exactly-once delivery still requires adapter-owned idempotency.
    • Lifecycle testing encountered an intermittent shutdown hang associated with backend connection state. We stopped at that gate and did not claim a deployable watcher.

    This suggests that useful queue mechanics exist, but the product still needs a supported, opt-in bridge into the visible Desktop task: atomic active-versus-idle admission, durable idempotency/cursor semantics, pause and watcher-death visibility, narrow authentication/permissions, and zero model activity while quiet.

    That would let an external message bus wake the task the user is already watching, rather than creating a separate invisible headless seat.

  5. hueniverse commented on Sep 2, 2026

    @hueniverse

    Our workflow relies on hacks and workaround for this limitation to enable us to run a Codex and Claude Code sessions in pairs, talking to each other. On the Claude side we use the monitor feature, but on Codex we need to override the cli settings to hijack traffic.

  6. saksham10arora-dotcom commented on Oct 18, 2026

    @saksham10arora-dotcom

    Adding a concrete finding that might make this smaller to ship than it looks: the desktop app already contains a wake path for visible threads. What is missing is only a way to put a message into a thread's queue from outside.

    Use case

    I run Claude Code and Codex Desktop as a pair on the same project. They talk through a shared local mailbox (an append-only JSONL file; the tool is public at https://github.com/saksham10arora-dotcom/agent-sync). Claude Code can watch that file with its Monitor tool, so it reacts to a new message within a second. Codex can only check the mailbox when a scheduled automation fires (every 20 minutes for me) or when I type. So every exchange where Codex has to answer costs up to 20 minutes, and an idle Codex task still spends tokens on each scheduled check that finds nothing.

    This is the same gap described above: the event exists outside, and the visible Codex thread has no way to hear it.

    What the desktop app already has

    Inspected read-only on macOS, ChatGPT desktop app 26.1013.31256 (the Codex tab), in Contents/Resources/app.asar. The app registers the codex URL scheme (CFBundleURLSchemes in Info.plist), and its deep link parser has these threads routes:

    1. codex://threads/new with prompt, mode, path, browserUrl or originUrl query parameters opens a new thread with the composer pre-filled. In my test it did not send, so a person still has to press Send.
    2. codex://threads/<id> opens an existing local conversation.
    3. codex://threads/wake/<conversationId> is parsed into { kind: "wakeQueuedThread", conversationId }. The parser only accepts exactly two path segments with no query string and no fragment. The handler deduplicates repeated wakes for the same conversation and retries up to 3 times with a 1 second backoff (it logs "Failed to wake queued thread" on failure).

    As far as I can tell, route 3 processes follow-up messages that are already queued on a thread (for example a message typed while a turn was running). It does not accept a payload, so an outside process cannot add anything for it to process. I found no supported way to enqueue a follow-up from outside the app, which matches the remote-control findings in the issue body: the injectable daemon only reaches headless threads.

    Suggested minimal change

    Since the queue and the visible-thread wake already exist, one of these would close the gap without a new transport:

    • Option A: let the wake route carry a message, for example codex://threads/wake/<conversationId>?prompt=<urlencoded text>, which appends the text as a queued follow-up and then runs the existing wake.
    • Option B: a separate codex://threads/<conversationId>/enqueue?prompt=... route that only enqueues, keeping wake unchanged.
    • Option C: the same "enqueue a follow-up for conversation X" operation on the local app-server or remote-control socket, for tools that prefer a socket to a URL.

    Safety

    Deep links can be opened by any local app or web page, so this should be off by default:

    • an opt-in setting per thread (or per project), off until the user enables it;
    • a per-install secret token required in the link (shown in that setting), so a web page cannot inject into a thread;
    • clear marking of injected messages in the thread, plus a rate limit;
    • for automations specifically, the injected text could arrive as data for the next turn rather than as user instructions.

    Workarounds today

    • a second, frequent automation that only reads the mailbox and exits when it is empty (still polling, still costs tokens per check);
    • codex://threads/new?prompt=... plus a human pressing Send.

    Happy to test a build or share more detail on the pairing setup if that helps.

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

    CLIIssues related to the Codex CLIappIssues related to the Codex desktop appapp-serverIssues involving app server protocol or interfacesenhancementNew feature or requestremote

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions