Interactive CLI/TUI: opt-in local ingress for trusted local controllers (avoid PTY input emulation)
What feature would you like to see?
Expose an opt-in local ingress for interactive Codex CLI/TUI sessions, so another trusted local process can safely deliver input to the active session using real session semantics rather than PTY/keyboard emulation.
This is intentionally narrower than broad “remote control” and broader than “support one MCP notification”. The need is:
I have an already-running interactive Codex CLI/TUI session on my machine. I want a trusted local controller to hand it structured work or a follow-up message without pretending to be a keyboard.
Why this matters
Today, local orchestration around a running Codex TUI session often falls back to terminal injection:
send-keyspaste-bufferpipe-pane -I- direct PTY writes
These all have the same class of problems:
- text can land in the draft without actually submitting
- injected text can race with a human actively typing
- attribution, dedupe, and replay are ad hoc
- wrappers are forced to emulate terminal input instead of using real session/thread semantics
In practice this makes local automation brittle. It also discourages safer local integrations because the only widely available option is “pretend to be the terminal”.
This matters for:
- local orchestrators coordinating multiple Codex/CLI workers
- tmux- or terminal-based multi-agent workflows
- editor/terminal wrappers
- accessibility tooling
- local remote-control shells
- background workers that need to hand work back to a human-visible Codex session
Why existing features are close, but not enough
Codex already has the right conceptual model in the programmable surfaces:
thread/startthread/resumethread/forkturn/startturn/steerturn/interrupt
Those are exactly the kinds of semantics local controllers want.
The gap is that there does not appear to be a supported way to apply equivalent semantics to an already-running interactive Codex CLI/TUI session on the same machine.
Current adjacent features do not fully solve this:
- App Server / SDK: strong control model, but they do not obviously provide a documented “attach this existing interactive TUI session as a local controllable endpoint” workflow.
- MCP: great tool plane, but not a documented receive-side transport into the active TUI thread.
- Event hooks /
notify: useful for observation and orchestration, but outbound-only. - Remote control / mobile proposals: related, but they target remote supervision/control rather than the local trusted-controller case.
What I expected
At least one supported local-first path such as:
- An opt-in local IPC listener for interactive sessions
- for example:
codex --listen uds:///path/to/session.sock - local-only by default
- receives real turn/draft/steer operations instead of raw keystrokes
- A local CLI control command for live interactive sessions
- for example:
codex tui send --session <id> --text "..." [--submit] - optionally
--append-draft,--steer, or--interrupt
- A documented way to bind an interactive TUI session to App Server semantics
- so a local controller can attach to the same session/thread and send real turns
What I observed
For the stock interactive CLI/TUI, the practical fallback today is still terminal input emulation.
That leads to concrete failure modes:
- draft text inserted but not submitted
- active human typing gets mixed with injected content
- no built-in sender identity or audit trail at the ingress point
- orchestrators need terminal-state heuristics instead of stable session APIs
Why this is worth solving
This would unlock a cleaner class of local integrations without forcing people into cloud- or network-oriented remote-control flows.
Benefits:
- safer local orchestration for long-running interactive sessions
- less pressure to build wrappers around PTY injection
- easier composition with local tools and terminal multiplexers
- better accessibility and automation ergonomics
- stronger alignment between the interactive TUI and the documented thread/turn model already present in App Server / SDK
Suggested direction
My strong preference would be:
- keep it opt-in
- keep it local-only by default
- reuse existing thread/turn semantics where possible
- avoid introducing a second unrelated message model just for the TUI
If full ingress is too large, a useful partial step would be richer documented local event coverage for orchestration wrappers, for example:
turn-startedturn-interruptedapproval-requestedapproval-resolvedidledraft-presence-changed
That would at least let local controllers make safer delivery decisions without scraping terminal output.
Related issues
This feels related to, but distinct from:
#15299inbound MCP notifications into an active CLI session#14722sync CLI and app-server sessions#15320app-server TUI not fully reflecting external live turns on shared thread#14693no-interrupt typing / queued-draft safety while Codex is running#9224remote control from phone/web#2109event hooks
The difference here is the focus on trusted local ingress for an already-running interactive session, without relying on PTY injection and without requiring a separate remote/mobile product surface.
Additional information
If useful, I can provide a more explicit repro matrix showing:
- PTY injection behavior today
- the specific failure modes in interactive terminal workflows
- the minimal event/ingress API surface needed by a local orchestrator
11 Comments
Small concrete repro matrix to make the integration gap more testable.
What local wrappers/orchestrators do today
When they need to hand work to an already-running interactive Codex CLI/TUI session, the practical options are usually:
send-keyspaste-bufferpipe-pane -IThose differ in mechanics, but from the session’s perspective they are all “pretend to be terminal input”.
Failure modes with current PTY-style approaches
1.
send-keysObserved/expected class of problems:
2.
paste-bufferObserved/expected class of problems:
3.
pipe-pane -IObserved/expected class of problems:
Desired behavior instead
What local orchestrators actually need is a supported ingress with real session semantics, for example:
And ideally:
That is why this request is less about any one transport and more about exposing a proper local control surface for interactive sessions.
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Thanks — I reviewed
#15299.I think it is related, but not a true duplicate.
My read of the distinction:
#15299is asking for a specific inbound MCP-notification path into an active Codex CLI session.#15355is asking for a broader opt-in local ingress for an already-running interactive CLI/TUI session, ideally reusing thread/turn semantics and not depending on any one transport.In other words,
#15299is one possible solution shape under the larger problem, but not the whole problem.Some examples that would satisfy
#15355without necessarily solving it via MCP notifications:codex tui send ...command for a live sessionI opened this separately because the core integration gap exists even outside MCP-specific channel/inbox use cases.
That said, if maintainers would prefer to consolidate discussion into
#15299, I’m happy to adapt and close this one.I think the smallest useful first step here is a local-only, opt-in control surface for live interactive sessions, with a phased rollout instead of treating full ingress as all-or-nothing.
If a full local ingress endpoint is too large initially, a phased shape like this seems aligned with Codex’s existing thread/turn direction:
idlebusyturn-startedturn-completedapproval-requestedapproval-resolvedturn.interrupt/turn.steerpath into a live sessionturn.start/ submit semantics once the interactive session model is settledThe reason I think this shape matters is that local wrappers/orchestrators are currently forced into PTY input emulation (
send-keys,paste-buffer,pipe-pane -I, direct PTY writes). Those are all the same class of primitive from the session’s perspective: “pretend to be terminal input.” That is where the brittle failure modes come from:If there eventually is a full ingress endpoint, the cleanest model seems like a session-scoped local endpoint that maps into Codex’s own turn semantics rather than raw stdin. But even the smaller first step of status/events plus
interrupt/steerwould already make local wrappers and sidecars much safer.If maintainers prefer, I’m happy to narrow the request further to just the event surface plus
interrupt/steeras a first phase.app server?
I have a small implementation branch for this shape here:
Scope of the branch:
codex --experimental-inbound-channel <SOCKET>for Unix TUI sessions.0600permissions.{"text":"Please review the current diff","source":"local-client"}.!is submitted as user text, not treated as the TUI shell shortcut.source/text_lenmetadata.Validation run locally after rebasing onto current
main:I saw the contributing policy says external contributions are invitation-only, and the API currently rejects PR creation from this account. If this direction fits the maintainers' intended design, I am happy to open/retarget a PR in whatever form you prefer.
This distinction matters for remote/mobile sidecars too. Without a semantic local ingress, every wrapper eventually falls back to PTY-shaped input, and then the controller cannot tell whether text was accepted as a turn, left in draft, raced with human typing, or blocked behind an approval gate.
A useful minimal surface would be both state and admission control, not only delivery:
idle/busyawaiting_inputblocked_on_permission { prompt_id, requested_capability }input_accepted { turn_id, source }input_rejected { reason }interrupt_accepted/interrupt_rejectedThe
input_rejectedside is important. For same-session phone/desktop control, silent queueing is often worse than a hard NACK, because it creates a false liveness signal while the real TUI process is blocked somewhere else.+1 to @Snailflyer on remote/mobile sidecars. There's a small cluster of community wrappers that have shipped on exactly the PTY-injection model the original issue describes — tmux send-keys / paste-buffer / direct PTY writes — and we hit the exact failure modes you list (text-in-draft-not-submitted, races with human typing, approval-gate state scraped from the terminal buffer instead of read from a session API).
A few examples, all PTY-based today: CodeVibe (iOS/Android wrapper for codex/claude/gemini), codex-pocket (codex-specific mobile), pikiclaw (web UI for claude). Same class of brittle.
The admission-control primitives Snailflyer sketched (
input_accepted {turn_id, source},input_rejected {reason}) would let every one of these drop their pane-scraping heuristics and behave deterministically. Happy to share concrete failure-mode repro logs if it'd help shape the API.strong +1, and the failure matrix @sidkandan posted is exactly right. sharing from the other side of it: i maintain repowire (a cross-runtime agent mesh) and the codex transport currently falls back to tmux injection precisely because this ingress doesnt exist.
the thing that bit me and pushed me toward fail-loud: you cant tell from a
send-keysorpaste-bufferwrite whether the text was accepted as a turn, left in the draft, raced with the human typing, or swallowed behind an approval prompt. i ended up only marking a delivery as injected when a separate hook acks it back, which is just papering over the missing semantics youre asking for here.a real local ingress with observable accept/reject would let wrappers like this drop the PTY hack entirely. id move repowires codex path onto it day one.
Related: the MCP notification path is a narrower opt-in local ingress primitive for trusted controllers, without PTY input emulation.
Patch: https://github.com/openai/codex/compare/main...alexfrmn:upstream-pr-mcp-surface-notifications?expand=1
This proposal maps closely to LS work on governed agent ingress, durable event identity, and replayable coordination.
The central distinction is:
text appeared in terminal
≠ message was accepted by the session
≠ turn was started
≠ requested action was authorized
PTY injection collapses all of these stages into an unreliable stream of keystrokes.
A structured local ingress could instead accept a typed request:
{
"schema_version": "local-session-ingress/v0.1",
"request_id": "local-controller:1842",
"sender": {
"controller_id": "local-orchestrator",
"process_identity": "pid:4412",
"trust_scope": "same-user-local"
},
"target": {
"session_id": "codex-thread-123",
"trajectory_id": "project-alpha"
},
"operation": "START_TURN",
"payload": {
"content_type": "text/plain",
"text": "Review the latest integration-test result"
},
"delivery": {
"deduplication_key": "local-controller:1842",
"expires_at": "2026-06-24T22:00:00Z"
},
"authority": {
"may_deliver_input": true,
"may_interrupt_turn": false,
"may_approve_actions": false
}
}
A safe processing path could be:
local IPC request
→ authenticate controller
→ validate target session
→ check operation capability
→ deduplicate
→ inspect current TUI state
→ accept / queue / reject
→ create a real thread item or turn
→ emit receipt
The key invariant would be:
«Permission to submit text to a session must not imply permission to approve tool execution, change sandbox policy, or interrupt an active turn.»
Capabilities should therefore remain separate:
A local controller could be allowed to submit follow-up work while being explicitly forbidden from resolving approvals.
A delivery receipt might look like:
{
"request_id": "local-controller:1842",
"session_id": "codex-thread-123",
"status": "QUEUED",
"reason": "active human draft present",
"thread_item_id": null,
"turn_id": null
}
This makes important states observable:
RECEIVED
→ VALIDATED
→ QUEUED
→ DELIVERED
→ SUBMITTED
→ TURN_STARTED
→ PROCESSED
instead of treating successful socket or PTY writing as successful task delivery.
Human-input collision also needs an explicit rule. For example:
human draft present
→ HOLD or QUEUE
not silently merge both text streams.
A deterministic conformance fixture could test:
Related LS work:
https://github.com/safal207/LS/issues/594
https://github.com/safal207/LS/issues/595
https://github.com/safal207/LS/issues/597
https://github.com/safal207/LS/pull/651
Would a small vendor-neutral "LocalSessionIngressRequest" and "IngressDeliveryReceipt" fixture help test controller authentication, capability separation, draft collisions, deduplication, and real turn semantics without PTY emulation?