Event-driven wakeup when background exec sessions complete

Open 💬 5 comments Opened Jul 10, 2026 by praveenperera
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What variant of Codex are you using?

cli

What feature would you like to see?

Description

Long-running commands currently require the model to poll write_stdin, remain inside a long-running tool call, or delegate monitoring to a subagent. Repeated polling creates additional model turns, while a monitoring subagent adds its own context and reasoning costs.

Codex already waits for background processes asynchronously and emits an ExecCommandEnd event when they exit. It would be useful to optionally wake an idle thread when that event occurs.

Proposed behavior

Add an opt-in completion mode to exec_command:

{
  "cmd": "just test",
  "yield_time_ms": 1000,
  "on_exit": "wake"
}

on_exit could accept:

  • "none": preserve the current behavior and require explicit polling
  • "wake": resume the thread once the background process exits

When "wake" is selected and the command yields a background session, Codex should:

  1. Allow the current turn to finish and become idle.
  2. Wait for the process without model inference.
  3. Enqueue a bounded completion notification when the process exits.
  4. Start one continuation turn containing the exit status, duration, and a capped output summary.
  5. Avoid a duplicate continuation if completion was already observed through write_stdin.

The tool response should make it clear that automatic notification is registered and polling is unnecessary.

Motivation

This would make long-running tests, builds, deployments, and similar commands event-driven instead of polling-driven. It would reduce unnecessary model turns and remove the need to create “babysitter” subagents solely to monitor process completion.

A recent agent run used many high-effort subagents and thousands of model cycles, accumulating roughly 470 million subagent tokens. Process monitoring was not the only source of that usage, but native completion wakeups would eliminate one avoidable class of polling and monitoring work.

Important cases

  • Existing behavior remains unchanged when on_exit is omitted.
  • Completion wakes an idle thread exactly once.
  • Completion arriving around the turn’s answer boundary is not lost.
  • Manually observing completion does not cause a later duplicate wakeup.
  • Injected command output is strictly bounded.
  • Multiple processes completing close together do not create an unbounded number of continuation turns.
  • Cancellation and session shutdown do not produce stale wakeups.

This could reuse the existing background exit watcher and pending-work scheduling mechanisms rather than requiring an active model turn while the process runs.

Additional information

_No response_

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 1 month ago

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

  • #31935

Powered by Codex Action

praveenperera · 1 month ago

POC implementation https://github.com/praveenperera/codex/tree/32188-poc-impl @fcoury

More fleshed out implementation here, along with a small UI change so the subagent model and reasoning level is displayed in the TUI: https://github.com/praveenperera/codex/tree/wait-fork

markgerrard · 1 month ago

Why stock can't do this today, cited to lines (verified at rust-v0.144.5, still true at rust-v0.145.0-alpha.20):

  • When a backgrounded unified_exec process exits while the session is idle, the exit-watcher's tail is UI-only: it emits an outbound ExecCommandEnd client event and stops.
  • The TUI completion handler drops that event exactly in the idle case — tui/src/chatwidget/command_lifecycle.rs:154, if !self.bottom_pane.is_task_running() { return; }.
  • The stock idle-start path (maybe_start_turn_for_pending_work) is mailbox-driven only; a background exit is not a mailbox source, and nothing else submits work for it. The wait works; the wake doesn't exist.

Related reports that are this same gap from the user side: #14314, #22957, #12033, and #33542 (config-level flavor of this request); #31935 is the polling-pain cousin. I filed #33712 before finding this thread and am closing it in favor of here.

Implementation status: we run codex as a multi-agent orchestrator and hit this daily, so we built the wake path — currently in production soak as our daily driver, and we intend to open it as a small stacked series once the soak completes. Behind a default-off flag: background exit while idle wakes the session and resumes the original thread with exit code and output rendered as turn input; exits during an active turn are queued and drained when it finishes; the wake routes through the serialized submission loop as an internal op, so aborts and capacity limits compose without new race surface.

@praveenperera your "Important cases" list turned out to be the right acceptance criteria — each of them is closed in our test matrix (exactly-once wake, completion at the answer boundary not lost, no duplicate after manual observation via write_stdin, bounded injected output, multiple near-simultaneous exits batched into one continuation, no stale wakeups across cancellation/shutdown). Production forced two cases onto the list that it doesn't have yet, sharing them here since they cost us the most review rounds: kill/interrupt attribution (an exit induced by a kill, interrupt, or session teardown must not wake — otherwise every abort path produces a phantom continuation) and capacity-ceiling composition (what a wake does when the session is at its concurrency ceiling: we gate and degrade to today's stock behavior rather than overshoot).

The PoC (32188-poc-impl) hits the right seams — completion_wakeup, the input-queue hook, a completion fragment — clearly written by someone who read this code carefully. On the API surface: per-call on_exit is arguably the better surface (finer intent, no global behavior change); our config flag was the orchestrator-shaped choice. The honest technical point is that our core is trigger-agnostic — the queue/wake/attribution machinery doesn't care whether the opt-in arrives per-call or per-config, so it can sit under your surface, ours, or both. If you're open to it, I'd be glad to converge on one stacked series: your API shape on our hardened core. Design notes available in the meantime.

Alex-R-A · 1 month ago

Independent reproduction on stock Codex CLI 0.145.0

Confirmed twice on macOS arm64. One test used a real externally triggered detector rather than a fixed sleep, so the failure is not specific to timer commands.

Reproduction

  1. Start a command through tracked unified exec and let it yield as still running.
  2. Allow the Codex turn to finalize so the TUI becomes idle.
  3. Let the tracked process exit normally.
  4. Observe that its completion and output are retained, but no new model turn starts.
  5. Send a user message. The resulting turn can then inspect the already completed process.

Result: 0 automatic wakes in 2 controlled trials.

One recorded timeline:

  • Codex turn finalized: 2026-07-26T17:10:10.676Z
  • External event caused the tracked process to complete: shortly after 2026-07-26T17:11:17Z
  • Human message started the next Codex turn: 2026-07-26T17:17:03.600Z

No Codex inference occurred during the 346.6 seconds between the external event and the human message.

Control

A tracked sleep 2 completion reached Codex when the originating model turn was still active. The background process manager and completion-event path therefore work. The missing transition is specifically from an idle completion to new model inference.

Source confirmation

In stable 0.145.0 and current main, on_command_execution_completed records the process end and then returns when bottom_pane.is_task_running() is false. That idle path does not enqueue pending work or start a turn. This matches the observed behavior.

Requested behavior

An opt-in wake mode should:

  • Let the current turn finish while the process continues without model inference.
  • Start exactly one continuation turn if the tracked process exits while the thread is idle.
  • Queue the completion if another turn is active, rather than starting concurrent inference.
  • Include bounded exit status, duration, and output.
  • Suppress a later wake if write_stdin already consumed the completion.
  • Preserve completions that race with the final-answer boundary.
  • Coalesce multiple near-simultaneous completions.
  • Avoid wakeups caused by cancellation, kill, interrupt, or session shutdown.

An optional exit-code allowlist would also help finite watchers suppress expected timeout exits while still waking for real events. Its default can remain all exit codes so failed builds and tests still wake.

This is useful beyond timers: long builds, test suites, deployments, CI monitors, and external event detectors currently require polling, an occupied model turn, a human nudge, or a custom client that explicitly starts another turn.

lunara69-ctrl · 4 days ago

+1 from an independent Codex CLI audit.

The distinction between “waiting” and “model inference caused by waiting” appears important.

In one audited workflow I found 58 WAIT/POLL continuations with no new useful information. They accounted for 6,787,563 input tokens (~117k per continuation on average), because each wake-up occurred against an already large parent context.

This suggests that event-driven wakeup could have a larger impact than simply reducing tool-call chatter: it removes an entire class of parent inference whose semantic content is effectively zero.

For benchmarking this feature, I would suggest measuring not only wall time or number of polls, but:

WAIT_MAIN_INPUT = total parent-model input caused exclusively by waiting for an external operation to reach a terminal state.

For an ideal runtime-managed wait, that metric should be 0 until an actual completion event exists.