Repository navigation
Expose a privacy-minimal output-only human-input lifecycle from codex-core #39426
Description
What variant of Codex are you using?
Codex App, IDE Extension, and CLI.
What feature would you like to see?
Please expose an optional, output-only lifecycle for the moments when Codex is
actually waiting for a human permission decision or supported user input.
The lifecycle should be produced once in codex-core, so App, IDE Extension,
and CLI clients share the same semantics. It should emit only:
permission pendingand a matchingresolved;question pendingand a matchingresolved.
Pending should be emitted only after policy, cached approvals, hooks,
automated review, and other automatic routing have finished and the request
will actually be shown to a human.
Resolved should be emitted exactly once, without the decision or answer,
when the owning client receives a response or the request is cleared by
decline, cancellation, replacement, interruption, turn completion, abort, or
session shutdown.
This should cover permission requests as well as blocking
request_user_input and MCP elicitation questions.
The observer must remain strictly notification-only. It must have no protocol
or code path to approve, reject, select an option, or submit text. The user
continues to respond only in the Codex client that owns the request.
Privacy and authority boundary
The output should contain only a generic kind and an internally generated
opaque request ID. It should never contain request content, filesystem paths,
approval reasons, question text, options, tool input, model output, user
answers, or approval outcomes. Lifecycle data should not be written to
telemetry or long-lived logs.
An optional local transport should be output-only from Codex. Observer input
must never be read and must not be able to influence Codex behavior.
Deterministic cleanup
The lifecycle needs bounded source identity, monotonic sequencing, a heartbeat,
and an authoritative reconnect snapshot containing only requests that are
still active.
EOF, an unknown version or event, a sequence gap, a collision, process loss,
or restart should clear pending state by default. Reconnect must not replay
historical notifications. A still-active request should retain its opaque
request ID across a fresh transport source so a receiver can restore visual
state without replaying sound for the same unresolved request.
Why existing surfaces are not sufficient
App Server exposes typed requests and matching resolution on the owning client
transport, but a separate read-only observer cannot attach to every existing
App, IDE, and CLI task.
PermissionRequest hooks run before the final human-routing decision, so a
hook invocation does not prove that Codex is waiting for a person. They also
do not provide the matching generic resolution needed to remove a notification
deterministically.
A one-shot notification event cannot reliably clear stale pending state when
the Codex process or observer disappears.
Additional information
This is related to #28833, which requests a post-routing approval signal for
passive notifications. This request additionally covers paired no-outcome
resolution, supported blocking questions, shared core ownership, reconnect,
and deterministic source-loss cleanup.
A software-only reference prototype has validated the feasibility of the
core-owned lifecycle and a strict downstream receiver, including permission
and question round trips, privacy boundaries, duplicate and replay handling,
reconnect, restart, observer loss, collision handling, and stale cleanup.
Technical design and test details can be provided privately if useful.
The feature can be disabled by default and should not change existing approval
flows or require UI scripting, keyboard automation, or accessibility
automation.
Activity
owieschon commentedon Sep 6, 2026
Reproduced a downstream consequence of this gap: Superset reports a Codex terminal as working while its native approval dialog is waiting for a response.
The live control used Superset 1.26.0 and the app-bundled Codex 0.153.4 executable on macOS arm64. Codex ran through Superset's existing wrapper with
--ask-for-approval on-request --sandbox read-only. Project-local command hooks recorded lifecycle events and returned{}without deciding permission or updating Superset state.The reproduction was a single harmless command:
The native dialog was visibly pending when the host state was sampled. Accepting once executed the command and completed the same conversation. These are observations from one trace; the prolonged wait included an operator interruption and is not a latency benchmark.
PermissionRequestStartExecApprovalPostToolUse1234567StopStopThe process-scoped TUI log recorded the acceptance with an execution ID, but contained no corresponding approval-prompt record. The live run used the app-bundled binary; a separate npm installation reporting 0.153.4 was version-checked, not exercised in this trace.
Registering
PermissionRequestand clearing it withPostToolUsewould not preserve the consumer's semantics. The request hook runs before automatic or user resolution. The post-tool hook observes successful, completed execution. It cannot distinguish actual human attention or clear immediately on every resolution path. These additional paths were source-reviewed, not live-tested here.Current main already has the required semantics in app-server. At
ad931a45, approval requests follow automatic routing; the existingserverRequest/resolvednotification provides matching resolution. The TUI receives these throughhandle_app_server_event, which bypasses the opt-in session recorder.Would maintainers support a fixed, redacted projection of that existing request/resolved pair through
CODEX_TUI_RECORD_SESSION, or is another terminal-observer surface preferred? The projection could retain method and thread/request correlation while excluding request content and response decisions. Approval authority would stay with the owning client.Focused acceptance cases for that exposure:
The existing session-log tests provide a narrow regression surface. This builds on the app-server lifecycle and relates to #33020 without introducing another approval protocol.
The control's final response was
1234567, rather than the requested completion marker. Command execution and lifecycle completion are verified; exact-marker compliance is not. Diagnostic excerpts have cost metadata and unrelated account/configuration information removed.hippoley commentedon Sep 28, 2026
The output-only observer boundary in this proposal looks right to me. One thing I would preserve in the lifecycle shape: "resolved" should not collapse human completion and machine cleanup into the same semantic outcome.
I hit the same ambiguity while building a small external human-boundary control plane. The failure mode was subtle:
That is enough to create false audit state even if the observer never receives the answer itself.
A privacy-minimal lifecycle can still distinguish provenance without exposing the decision:
No command/question text, answer, or approval outcome is required.
In the prototype I ended up enforcing the same rule at the canonical state layer:
and a human-only boundary rejects non-human provenance on the normal resolution path.
Implementation + regression tests, if a concrete shape is useful:
hippoley/HumanQueue#17
Separately, for native-host evidence I also found that
PermissionRequestalone is not proof of human attention andPostToolUseis too late to represent resolution. That matches the distinction already made in the comment above: the best source is the core-owned request/resolved lifecycle itself.So I think the minimal observer contract is strongest if it exposes request identity + lifecycle provenance, while remaining completely output-only and content-free.