Code mode can report completion with live nested exec sessions but no model-visible handles
What version of the Codex App are you using (From “About Codex” dialog)?
Reproduction: 26.721.41059
Original observed incident: Desktop bundle version 26.715.12143
What subscription do you have?
ChatGPT Plus
What platform is your computer?
Darwin 25.5.0 arm64 arm
What issue are you seeing?
Code mode can report Script completed while nested exec_command processes remain live and every model-visible session_id for those processes has been discarded.
A nested command can outlive its initial yield window and remain stored in the unified-exec manager. JavaScript receives its logical session_id, but code that keeps or emits only .output can discard that ID. The cell then returns successfully and reports Script completed while the process remains live.
At that point, the model has no ID with which to inspect, continue, or terminate the process, and the current tool surface exposes no separate operation for recovering the lost ID.
The defect is a terminal code-cell result that leaves manager-owned live work without a model-visible control handle.
This is the narrower lost-handle case within the broader wrapper/process mismatch reported in #34866.
What steps can reproduce the bug?
The failure reduces to a code-mode JavaScript cell that starts nested commands and retains only each result's .output:
const outputs = (await Promise.all([
tools.exec_command({ cmd: "printf orphan-a; sleep 60", yield_time_ms: 250 }),
tools.exec_command({ cmd: "printf orphan-b; sleep 60", yield_time_ms: 250 }),
])).map(({ output }) => output);
text(outputs.join("|"));
The reduced case reports completion:
Script completed
Wall time ...
Output:
orphan-a|orphan-b
The two commands may still be live in the unified-exec manager, but their logical session IDs are missing from the model-visible result because the cell retained only .output.
The before-state is preserved in an executable negative reproduction.
What is the expected behavior?
A terminal code-cell result should include the logical session IDs of still-live nested commands created by that cell:
Script completed
Background sessions still running: 6306, 11236
Wall time ...
Output:
orphan-a|orphan-b
6306 and 11236 are illustrative logical session IDs rather than operating-system PIDs.
The processes may continue running. The model should retain the IDs needed to inspect, continue, or terminate them.
Because the completion path cannot know which returned handles JavaScript retained, the status should report the still-live sessions associated with the completing cell.
Observed real-world impact
This occurred during a real Codex Desktop session running bundle version 26.715.12143.
A code-mode cell launched two Playwright screenshot commands and a curl command concurrently, then emitted only each result's .output. After the Playwright commands yielded, the cell reported Script completed without their logical session IDs.
Codex launched replacement screenshot commands, which completed successfully, while the original process groups remained running for more than four days. The persisted process registry still contained their original commands and logical session IDs, even though both entries had "osPid": null.
At inspection time, the two abandoned jobs had an approximately 7.24 GB combined memory footprint:
<img width="829" height="86" alt="macOS Activity Monitor showing two surviving node processes at 3.62 GB each" src="https://github.com/user-attachments/assets/4f6c1a5a-3620-44ad-ad39-038a9f6e6a51" />
Activity Monitor showing the two surviving node processes at 3.62 GB each.
They required manual process-group termination outside Codex.
Why the IDs cannot be recovered
The terminal response identifies the completing cell, and its handler can access the unified-exec manager. The manager's live process entries do not retain which code-mode cell created each process, so after JavaScript discards a returned session_id, the handler cannot determine which live sessions belong to that cell.
At the verified upstream snapshot, handle_runtime_response formats terminal output without an exact-cell manager lookup.
Tested implementation
The implementation preserves the creating code-mode cell ID on each live ProcessEntry, queries the manager for still-live processes created by the completing cell, and includes their logical session IDs in the terminal status.
It excludes exited entries, sorts the IDs numerically, displays at most 64 IDs with an exact (+N more) suffix, reports them for terminal Result and Terminated responses, and leaves ordinary Yielded responses unchanged.
The lookup is read-only and does not change process lifetime or cleanup behaviour. The existing JavaScript result schema and nested call IDs remain unchanged.
The 64-ID cap remains a known limitation because IDs represented only by (+N more) cannot currently be recovered through a separate enumeration operation.
Tests cover multiple live sessions, deterministic ordering, overflow behaviour, exited-session exclusion, exact-cell isolation, truncation placement, and yielded-response neutrality.
Open questions
- Should live-session reporting apply to failed
ResultandTerminatedresponses, or only successful completion? - For exec-server-backed processes, is manager-observed liveness sufficient despite brief remote-exit lag?
Implementation and validation
Implementation: fix/code-mode-live-session-ids at 77e7e314.
Validation:
codex-core --lib: 2,093 passed, 0 failed, 0 skipped.- Local and Docker acceptance: on an earlier workspace with equivalent production behavior and the same 64-ID display value, 5 local passes, 4 Docker/Ubuntu passes, and one explicit remote-only skip for exit-then-exclude.
The skipped case leaves stale remote-exit exclusion unverified.
Two Wine/Bazel attempts (first, second) stopped during Bazel analysis before any test target was constructed because of the codex_rust_crate call-site/signature mismatch tracked in #35683. They produced no Wine-exec runtime result.
See the technical deep dive and validation history.
6 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Related to #34866, but independently actionable.
#34866 addresses the broader contradiction between outer cell completion and an inner process that remains active, including which continuation operation should be authoritative.
This issue isolates a narrower failure: code-mode JavaScript can discard every returned nested
session_id, after which the model has no operation for recovering those IDs even though the unified-exec manager still owns the live commands.The proposed change only restores those existing manager IDs for the completing cell. It does not redefine wrapper completion, process lifetime, or cleanup policy.
Validation evidence is pinned in two public documents:
The record includes:
76021678checkpoint;eb530466(run 30220464228): formatting, scoped fix, nine focused tests, diff check and clean worktree;TempDirremote exclusion, and 2 compatibility passes;codex-core --libsuite on latest implementation head77e7e3149df366236db2426596c23ebbe1d6bb48(run 30291034837): 2,093 passed, 0 failed, 0 skipped, with artifactcode-mode-full-suite-log;Both Wine attempts completed checkout and Bazel setup but failed during Bazel analysis before any test target was constructed because of the
codex_rust_cratecall-site/signature mismatch tracked in #35683. This preventscodex-command-runnerfrom being declared and stopscore-all-wine-exec-testfrom analyzing. No live-session acceptance assertion or runtime Windows skip executed, so these are reproducible repository build-graph failures rather than implementation-test failures.Windows/Wine boundary: even after that Bazel target becomes runnable, four live-session-handle acceptance cases return early via
skip_if_target_windows!because their nested commands use POSIX shell syntax, and the survivor case returns early viaskip_if_remote!because its hostTempDirpaths are unavailable to remote executors. A successful Wine suite would therefore validate the Bazel/Wine harness and broader shared suite, but would not mean the five live-session-handle acceptance assertions substantively ran against Windows.77e7e314contains the same production implementation as the independently boundedeb530466milestone; its only additional change is test-only Windows-target skip handling for the POSIX acceptance commands.Confirmed as a real-world occurrence on Codex App
26.727.40816 (6067)with bundledcodex-cli 0.146.0-alpha.9.2on macOS (Darwin 25.5.0 arm64).Two identical
ffmpegnested executions were started about 68 seconds apart. The first process was apparently no longer surfaced to the model before the replacement was launched. Both remained direct children of the Codexapp-serverfor approximately 7.5 hours:This matches the lost-handle/replacement-process failure pattern described here. The full incident report and sanitized command are in #36345, which I am closing as a duplicate of this issue.
seems like this would be better as a comment/comments in #34866 instead of stand alone issue. I'd suggest consolidating over there
Agreed. I copied the relevant reproduction, implementation context, and validation evidence into #34866 so discussion can continue there.