gpt-5.6-sol: "Script completed" is reported while the nested shell session is still running

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

What version of Codex CLI is running?

codex-cli 0.145.0

What subscription do you have?

Pro

Which model were you using?

gpt-5.6-sol

What platform is your computer?

Linux 6.12.24-Unraid x86_64 unknown

What terminal emulator and version are you using (if applicable)?

n/a - Codex CLI runs inside a Docker container on Unraid

Codex doctor report

{
  "schemaVersion": 1,
  "generatedAt": "1784774745s since unix epoch",
  "overallStatus": "warning",
  "codexVersion": "0.145.0",
  "checks": {
    "app_server.status": {
      "id": "app_server.status",
      "category": "app-server",
      "status": "ok",
      "summary": "background server is not running",
      "details": {
        "control socket": "/home/node/.codex/app-server-control/app-server-control.sock",
        "daemon state dir": "/home/node/.codex/app-server-daemon",
        "mode": "ephemeral",
        "pid file": "/home/node/.codex/app-server-daemon/app-server.pid (missing)",
        "settings": "/home/node/.codex/app-server-daemon/settings.json (missing)",
        "status": "not running",
        "update-loop pid file": "/home/node/.codex/app-server-daemon/app-server-updater.pid (missing)"
      },
      "remediation": null,
      "durationMs": 0
    },
    "auth.credentials": {
      "id": "auth.credentials",
      "category": "auth",
      "status": "ok",
      "summary": "auth is configured",
      "details": {
        "auth file": "/home/node/.codex/auth.json",
        "auth storage mode": "File",
        "stored API key": "false",
        "stored ChatGPT tokens": "true",
        "stored agent identity": "false",
        "stored auth mode": "chatgpt"
      },
      "remediation": null,
      "durationMs": 0
    },
    "config.load": {
      "id": "config.load",
      "category": "config",
      "status": "ok",
      "summary": "config loaded",
      "details": {
        "CODEX_HOME": "/home/node/.codex",
        "config.toml": "/home/node/.codex/config.toml",
        "config.toml parse": "ok",
        "cwd": "/home/node",
        "enabled feature flags": "shell_tool, unified_exec, shell_snapshot, code_mode_host, terminal_resize_reflow, sqlite, hooks, enable_request_compression, multi_agent, apps, tool_search_always_defer_mcp_tools, tool_suggest, plugins, in_app_browser, browser_use, browser_use_full_cdp_access, browser_use_external, computer_use, remote_plugin, plugin_sharing, image_generation, resize_all_images, skill_mcp_dependency_install, skill_search, mentions_v2, steer, guardian_approval, goals, collaboration_modes, tool_call_mcp_elicitation, auth_elicitation, personality, fast_mode, tui_app_server, remote_compaction_v2, workspace_dependencies",
        "feature flag overrides": "none",
        "feature flags enabled": "36",
        "log dir": "/home/node/.codex/log",
        "mcp servers": "2",
        "model": "gpt-5.6-terra",
        "model provider": "openai",
        "sqlite home": "/home/node/.codex"
      },
      "remediation": null,
      "durationMs": 0
    },
    "git.environment": {
      "id": "git.environment",
      "category": "git",
      "status": "ok",
      "summary": "git version 2.39.5",
      "details": {
        "PATH git #1": "/usr/bin/git",
        "PATH git #2": "/bin/git",
        "PATH git entries": "2",
        "git build options": "git version 2.39.5; cpu: x86_64; no commit associated with this build; sizeof-long: 8; sizeof-size_t: 8; shell-path: /bin/sh",
        "git exec path": "/usr/lib/git-core",
        "git version": "git version 2.39.5",
        "repo detected": "false",
        "selected git": "/usr/bin/git"
      },
      "remediation": null,
      "durationMs": 14
    },
    "installation": {
      "id": "installation",
      "category": "install",
      "status": "ok",
      "summary": "installation looks consistent",
      "details": {
        "PATH codex #1": "/usr/local/bin/codex",
        "PATH codex #2": "/usr/local/bin/codex",
        "PATH codex entries": "2",
        "current executable": "/usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/bin/codex",
        "install context": "npm (package /usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl, bin /usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/bin, resources /usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex-resources, path /usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex-path)",
        "managed by bun": "false",
        "managed by npm": "true",
        "managed by pnpm": "false",
        "managed package root": "/usr/local/lib/node_modules/@openai/codex",
        "npm update target": "/usr/local/lib/node_modules/@openai/codex"
      },
      "remediation": null,
      "durationMs": 74
    },
    "mcp.config": {
      "id": "mcp.config",
      "category": "mcp",
      "status": "ok",
      "summary": "MCP configuration is locally consistent",
      "details": {
        "configured servers": "2",
        "disabled servers": "0",
        "streamable_http servers": "2"
      },
      "remediation": null,
      "durationMs": 3185
    },
    "network.env": {
      "id": "network.env",
      "category": "network",
      "status": "ok",
      "summary": "network-related environment looks readable",
      "details": {
        "proxy env vars": "none"
      },
      "remediation": null,
      "durationMs": 0
    },
    "network.provider_reachability": {
      "id": "network.provider_reachability",
      "category": "reachability",
      "status": "ok",
      "summary": "active provider endpoints are reachable over HTTP",
      "details": {
        "ChatGPT base URL": "https://chatgpt.com/backend-api/ reachable (HTTP 404)",
        "reachability mode": "ChatGPT auth"
      },
      "remediation": null,
      "durationMs": 658
    },
    "network.websocket_reachability": {
      "id": "network.websocket_reachability",
      "category": "websocket",
      "status": "ok",
      "summary": "Responses WebSocket handshake succeeded",
      "details": {
        "DNS": "2 IPv4, 2 IPv6, first IPv4",
        "auth mode": "chatgpt",
        "connect timeout": "15000 ms",
        "endpoint": "wss://chatgpt.com/backend-api/<redacted>",
        "handshake result": "HTTP 101 Switching Protocols",
        "model provider": "openai",
        "models etag present": "true",
        "provider name": "OpenAI",
        "proxy env vars": "none",
        "reasoning header": "false",
        "server model present": "false",
        "supports websockets": "true",
        "wire API": "responses"
      },
      "remediation": null,
      "durationMs": 530
    },
    "runtime.provenance": {
      "id": "runtime.provenance",
      "category": "runtime",
      "status": "ok",
      "summary": "running npm on linux-x86_64",
      "details": {
        "commit": "unknown",
        "current executable": "/usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/bin/codex",
        "install method": "npm (package /usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl, bin /usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/bin, resources /usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex-resources, path /usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex-path)",
        "platform": "linux-x86_64",
        "version": "0.145.0"
      },
      "remediation": null,
      "durationMs": 0
    },
    "runtime.search": {
      "id": "runtime.search",
      "category": "search",
      "status": "ok",
      "summary": "search is OK (bundled)",
      "details": {
        "search command": "/usr/local/lib/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex-path/rg",
        "search command readiness": "file exists",
        "search provider": "bundled"
      },
      "remediation": null,
      "durationMs": 0
    },
    "sandbox.helpers": {
      "id": "sandbox.helpers",
      "category": "sandbox",
      "status": "ok",
      "summary": "sandbox configuration is readable",
      "details": {
        "approval policy": "Never",
        "codex-linux-sandbox helper": "/home/node/.codex/tmp/arg0/codex-arg0NojShT/codex-linux-sandbox",
        "execve wrapper helper": "/home/node/.codex/tmp/arg0/codex-arg0NojShT/codex-execve-wrapper",
        "filesystem sandbox": "unrestricted",
        "network sandbox": "enabled"
      },
      "remediation": null,
      "durationMs": 0
    },
    "state.paths": {
      "id": "state.paths",
      "category": "state",
      "status": "ok",
      "summary": "state paths and databases are inspectable",
      "details": {
        "CODEX_HOME": "/home/node/.codex (dir)",
        "active rollout files": "1253 files, 826974886 total bytes, 659995 average bytes",
        "archived rollout files": "0 files, 0 total bytes, 0 average bytes",
        "goals DB": "/home/node/.codex/goals_1.sqlite (file)",
        "goals DB integrity": "ok",
        "log DB": "/home/node/.codex/logs_2.sqlite (file)",
        "log DB integrity": "ok",
        "log dir": "/home/node/.codex/log (dir)",
        "memories DB": "/home/node/.codex/memories_1.sqlite (file)",
        "memories DB integrity": "ok",
        "sqlite home": "/home/node/.codex (dir)",
        "state DB": "/home/node/.codex/state_5.sqlite (file)",
        "state DB integrity": "ok",
        "thread history DB": "/home/node/.codex/thread_history_1.sqlite (missing)",
        "thread history DB integrity": "skipped (missing)"
      },
      "remediation": null,
      "durationMs": 11907
    },
    "state.rollout_db_parity": {
      "id": "state.rollout_db_parity",
      "category": "threads",
      "status": "warning",
      "summary": "rollout files and state DB thread inventory differ",
      "details": {
        "default model provider": "openai",
        "rollout DB active files": "1253",
        "rollout DB active rows": "1231",
        "rollout DB archive mismatches": "0",
        "rollout DB archived files": "0",
        "rollout DB archived rows": "0",
        "rollout DB duplicate DB paths": "0",
        "rollout DB duplicate rollout thread ids": "0",
        "rollout DB malformed file names": "0",
        "rollout DB missing active rows": "23",
        "rollout DB missing active sample": [
          "/home/node/.codex/sessions/2026/07/06/rollout-2026-07-06T09-11-07-019f3832-97a9-7711-a261-b2ca7452ce9f.jsonl",
          "/home/node/.codex/sessions/2026/07/06/rollout-2026-07-06T12-09-02-019f38d5-7b28-71c2-8375-d9575962d949.jsonl",
          "/home/node/.codex/sessions/2026/07/06/rollout-2026-07-06T13-11-04-019f390e-456e-78d2-bcf1-3a8b90a4cb37.jsonl",
          "/home/node/.codex/sessions/2026/07/06/rollout-2026-07-06T14-01-32-019f393c-7b3e-7350-8f94-504093a23807.jsonl",
          "/home/node/.codex/sessions/2026/07/06/rollout-2026-07-06T14-07-57-019f3942-5800-7441-b220-9ed6565a1160.jsonl"
        ],
        "rollout DB missing archived rows": "0",
        "rollout DB model providers": "openai=1231",
        "rollout DB rows": "1231",
        "rollout DB scan cap reached": "false",
        "rollout DB scan errors": "0",
        "rollout DB sources": "cli=421, vscode=360, exec=316, subagent:thread_spawn=134",
        "rollout DB stale row sample": "/home/node/.codex/sessions/2026/07/16/rollout-2026-07-16T10-55-35-019f6c11-d30b-7253-b714-918f21f81fb3.jsonl",
        "rollout DB stale rows": "1"
      },
      "issues": [
        {
          "severity": "warning",
          "cause": "rollout files are missing from the state DB",
          "measured": "23 active, 0 archived",
          "expected": "every rollout file has a matching threads row",
          "remedy": null,
          "fields": []
        },
        {
          "severity": "warning",
          "cause": "state DB rows point at missing or unusable rollout files",
          "measured": "1 stale rows",
          "expected": "every state DB rollout path is a file on disk",
          "remedy": null,
          "fields": []
        }
      ],
      "remediation": null,
      "durationMs": 269
    },
    "system.environment": {
      "id": "system.environment",
      "category": "system",
      "status": "ok",
      "summary": "OS language unavailable",
      "details": {
        "EDITOR": "not set",
        "VISUAL": "not set",
        "os": "Debian 12.0.0 [64-bit]",
        "os language": "unavailable",
        "os type": "Debian",
        "os version": "12.0.0"
      },
      "remediation": null,
      "durationMs": 0
    },
    "terminal.env": {
      "id": "terminal.env",
      "category": "terminal",
      "status": "ok",
      "summary": "terminal metadata was detected",
      "details": {
        "TERM": "xterm-256color",
        "color output": "enabled",
        "stderr is terminal": "true",
        "stdin is terminal": "true",
        "stdout is terminal": "true",
        "terminal": "unknown",
        "terminal size": "209x51"
      },
      "remediation": null,
      "durationMs": 0
    },
    "terminal.title": {
      "id": "terminal.title",
      "category": "title",
      "status": "ok",
      "summary": "terminal title default",
      "details": {
        "terminal title activity": "true",
        "terminal title items": "activity, project-name",
        "terminal title project source": "cwd",
        "terminal title project value": "node",
        "terminal title source": "default"
      },
      "remediation": null,
      "durationMs": 0
    },
    "updates.status": {
      "id": "updates.status",
      "category": "updates",
      "status": "ok",
      "summary": "update configuration is locally consistent",
      "details": {
        "cached latest version": "0.145.0",
        "check for update on startup": "true",
        "dismissed version": "0.145.0",
        "last checked at": "2026-07-23T00:33:57.797494430Z",
        "latest version": "0.145.0",
        "latest version status": "current version is not older",
        "npm update target": "/usr/local/lib/node_modules/@openai/codex",
        "update action": "npm install -g @openai/codex",
        "version cache": "/home/node/.codex/version.json"
      },
      "remediation": null,
      "durationMs": 400
    }
  }
}

What issue are you seeing?

On Codex CLI 0.145.0 using gpt-5.6-sol, a single long-running foreground shell command can expose two independently resumable handles:

  • an outer JavaScript execution cell_id, resumed with wait;
  • an inner shell/PTY session_id, resumed with write_stdin.

The outer JavaScript cell can finish and display Script completed while the nested shell process is still running. The same completed outer result can contain a live SESSION_ID, so the transcript presents completion and non-completion simultaneously.

A representative sequence is:

Script running with cell ID <CELL_ID>

After one or more calls to wait(cell_id=<CELL_ID>):

Script completed
...
SESSION_ID=<SESSION_ID>

At that point, the requested shell command has not necessarily exited. Codex must switch to write_stdin(session_id=<SESSION_ID>, chars="") to retrieve the remaining output and final exit status. When that write_stdin call is itself wrapped in another JavaScript execution, it can produce another outer cell_id.

This causes several problems:

  • Script completed can be mistaken for completion of the requested command rather than only completion of the JavaScript wrapper.
  • One logical foreground command requires switching between two continuation APIs.
  • Repeated empty polling consumes additional model/tool turns.
  • The final output and exit status can be lost when generated JavaScript forwards only r.output and omits r.session_id or r.exit_code.
  • Codex can rerun a command after interpreting wrapper completion as process completion.

The command being run is an ordinary synchronous foreground process. It does not background itself or create either continuation identifier.

What steps can reproduce the bug?

Uploaded thread: 019f8ce9-8102-7270-9497-e4d3f5123342

Alternatively:

  1. Start a new Codex CLI thread using gpt-5.6-sol.
  1. Instruct Codex to invoke its code-mode exec tool with exactly this raw

JavaScript, without replacing it with a direct shell invocation:

``javascript
// @exec: {"yield_time_ms": 2000, "max_output_tokens": 2000}
const r = await tools.exec_command({
cmd: "bash -lc 'echo START; sleep 30; echo FINISHED'",
yield_time_ms: 8000,
max_output_tokens: 2000
});
text(
OUTPUT=${r.output ?? ""});
text(
SESSION_ID=${r.session_id ?? "null"});
text(
EXIT_CODE=${r.exit_code ?? "null"});
``

  1. The timing is deliberate:

``text
outer exec yield: 2 seconds
inner exec_command yield: 8 seconds
shell command duration: 30 seconds
``

Therefore the outer JavaScript must yield before the nested shell tool,
and the nested shell tool must yield before the shell process exits.

  1. Inspect the raw rollout JSONL rather than only the rendered TUI

transcript.

  1. Observe the outer call first returning:

``text
Script running with cell ID <CELL_ID>
``

  1. Resume it using wait(cell_id=<CELL_ID>).
  1. Observe the resolving outer result returning approximately:

``text
Script completed
OUTPUT=START
SESSION_ID=<SESSION_ID>
EXIT_CODE=null
``

FINISHED has not been emitted and no exit code exists, so the requested
shell command remains active even though the outer result is labeled
Script completed.

  1. Continue the returned shell session using:

``javascript
const r = await tools.write_stdin({
session_id: <SESSION_ID>,
chars: "",
yield_time_ms: 30000,
max_output_tokens: 2000
});
text(
OUTPUT=${r.output ?? ""});
text(
SESSION_ID=${r.session_id ?? "null"});
text(
EXIT_CODE=${r.exit_code ?? "null"});
``

  1. Observe that this later continuation returns FINISHED and exit code

0. Confirm from the trace that the original shell command was invoked
only once.

Actual result:

One logical foreground command exposes an outer cell_id resumed through
wait and then an inner session_id resumed through write_stdin. The
outer layer reports Script completed while the inner process is still
running.

Expected result:

The requested command should have one authoritative continuation lifecycle,
or the intermediate status should explicitly distinguish
wrapper completed from command still running.

What is the expected behavior?

One requested foreground command should have one authoritative logical lifecycle.

Preferably, Codex should expose one stable continuation identifier from command launch through process exit and internally supervise any nested shell session.

At minimum:

  • completed should mean that the requested shell command has terminated, not merely that its JavaScript wrapper returned.
  • While the shell process remains active, the result should explicitly say that the wrapper completed but the command is still running.
  • The response should identify one authoritative next operation instead of requiring the model to infer whether to use wait(cell_id) or write_stdin(session_id).
  • Incremental output, final output, and the terminal exit code should remain associated with the same logical operation.
  • Polling a running shell session should not create another independently exposed continuation lifecycle.

For example, an intermediate state could be represented as:

{
  "wrapper_state": "completed",
  "process_state": "running",
  "session_id": 12345,
  "next_operation": "write_stdin"
}

The user-facing status should then say Wrapper completed; command still running, not simply Script completed.

Additional information

I did not explicitly enable code_mode or code_mode_only in my Codex configuration. The behavior occurs with the default tool surface selected for gpt-5.6-sol.

This report is not asserting that Sol's use of code mode is itself a bug. The reported bug is that one requested shell command exposes both the outer JavaScript continuation and the inner process continuation, while the outer wrapper can be described as completed before the requested command has exited.

Related but distinct: #13733 tracks the model-turn and token cost of repeated write_stdin polling. This issue concerns the additional outer cell_id lifecycle, contradictory completion semantics, handle switching, and possible loss of the inner continuation or terminal exit result.

View original on GitHub ↗

7 Comments

github-actions[bot] contributor · 1 month ago

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

  • #33816

Powered by Codex Action

Rubicj · 1 month ago

Related to #33816, but I do not believe this is an exact duplicate.

#33816 reports the model-side consequence: after receiving a live
session_id, GPT-5.6 loses ownership of that process, falsely infers
completion, and attempts a duplicate exec_command.

This issue reports the underlying execution-interface behavior that can
occur before any duplicate launch: code mode exposes an outer cell_id
and an inner session_id for one logical command, and the outer layer can
report Script completed while the inner shell process remains active.

The reproduction here requires only one command and remains a defect even
when the model correctly continues the inner session and never reruns the
command. #34866 may explain or contribute to the behavior in #33816, but
the requested runtime fix—one authoritative lifecycle, or explicit
wrapper/process states—is distinct from the model session-ownership
invariant requested there.

teamleaderleo · 1 month ago

I opened #35613 to isolate one independently fixable case within this report: code-mode JavaScript can discard nested session_id values while the unified-exec manager continues to own the live processes.

#34866 remains broader and also covers lifecycle contradictions beyond discarded handles. The focused issue restores model-visible handles from existing manager state without redefining wrapper completion or process lifecycle semantics.

The tested implementation retains the originating code-mode CellId on manager-owned process entries, queries the existing manager when that exact cell reaches a terminal response, and includes the matching live session IDs in the model-visible status.

Technical record:

teamleaderleo · 17 days ago

#35613 seems like a duplicate of #34866.

#34866 already describes the same lifecycle defect: a code-mode wrapper reports Script completed while its nested shell process remains active. It also explicitly identifies the same failure mode isolated in #35613—generated JavaScript can forward only r.output, omit r.session_id, and leave the model unable to continue the live process.

#35613 provides a narrower reproduction and a concrete implementation strategy—associating manager-owned processes with their originating cell and surfacing their live session IDs. But that is a scoped fix for the defect already reported in #34866, not a separate defect.

The reproduction, tests, and proposed implementation from #35613 are valuable additions here. I agree discussion should be consolidated in #34866 and #35613 closed as a duplicate.

teamleaderleo · 17 days ago

Validation evidence is pinned in two public documents:

The record includes:

  • the earlier local 20/20 candidate repetitions and 20/20 base repetitions on the pre-cap 76021678 checkpoint;
  • the exact bounded-head focused validation on eb530466 (run 30220464228): formatting, scoped fix, nine focused tests, diff check and clean worktree;
  • the local and Docker acceptance validation on an earlier workspace with equivalent production behavior and the same 64-ID display value (run 30217686056): 5 local acceptance passes, 4 remote passes, 1 explicit host-TempDir remote exclusion, and 2 compatibility passes;
  • the full codex-core --lib suite on latest implementation head 77e7e3149df366236db2426596c23ebbe1d6bb48 (run 30291034837): 2,093 passed, 0 failed, 0 skipped, with artifact code-mode-full-suite-log;
  • two Wine/Bazel attempts at the same implementation head: 30293323612 and 30296440567.

Both Wine attempts completed checkout and Bazel setup but failed during Bazel analysis before any test target was constructed because of the codex_rust_crate call-site/signature mismatch tracked in #35683. This prevents codex-command-runner from being declared and stops core-all-wine-exec-test from 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 via skip_if_remote! because its host TempDir paths 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.

77e7e314 contains the same production implementation as the independently bounded eb530466 milestone; its only additional change is test-only Windows-target skip handling for the POSIX acceptance commands.

yzh522163-prog · 16 hours ago

I reproduced the same failure mode on Windows Desktop with a real leaked local process.

Environment

  • Codex Desktop: 26.820.9563.0
  • Model: gpt-5.6-sol
  • OS: Windows 11 Pro, build 26200, x64
  • Execution path: Code Mode functions.exec calling nested tools.exec_command
  • This was a direct local shell command, not an MCP process.

Observed incident

A Code Mode cell awaited a nested tools.exec_command(...). The generated JavaScript then forwarded only:

text(r.output);

The nested Python command happened to contain an accidental non-terminating loop. After approximately 10.4 seconds, the outer tool result was rendered as:

Script completed
Wall time 10.4 seconds
Output:

No session_id or exit_code was surfaced to the model, so the turn continued as if the command had completed.

The underlying process tree remained alive:

codex.exe
  pwsh.exe
    python.exe
      python.exe

The leaf Python process continuously consumed about 4.2% of total CPU on a 32-logical-processor machine (roughly 1.3 cores) and about 760 MB private memory. It remained active for approximately 2 hours 57 minutes and caused sustained fan noise.

After I identified and terminated only that exact verified process tree, the retained command item finally resolved as failed with a recorded duration of 10,627,370 ms. Total sampled CPU dropped from 19.5% to 12.2%. No matching process remained afterward.

Why this evidence is useful

This is not only a confusing status string or a model retry. A normal local foreground command survived for hours because:

  1. the inner exec_command yielded a live session;
  2. the generated Code Mode wrapper discarded every result field except output;
  3. the outer cell reported Script completed; and
  4. no ownership/cleanup boundary reclaimed the still-live nested session.

The runtime should not emit an unqualified terminal outer status while owned nested work remains live. At minimum, it should preserve and surface the live session_id/non-terminal state. Preferably, the outer cell should retain ownership and either await the nested session or terminate it on cell/turn abandonment.

I have omitted local usernames, project paths, image contents, and raw rollout logs from this public report.

byte271 · 10 hours ago

I traced this against current openai/codex main (41d3dc56a0, #41239) and found a concrete lifecycle split that matches this report.

UnifiedExecProcessManager already knows whether the nested process is still alive. After exec_command, a still-running process returns ProcessStatus::Alive with process_id = Some(...):

https://github.com/openai/codex/blob/41d3dc56a0e1de47e30a9585c1b49253c082f8f7/codex-rs/core/src/unified_exec/process_manager.rs#L647-L651

So the manager-side state is internally consistent: the process is not done. The contradiction appears because wrapper/code-mode completion is a second lifecycle view that is not required to agree with that Alive state. In other words, Script completed can be emitted while unified-exec still owns a continuation handle.

I built an observation-only reproduction around that exact boundary (it does not patch Codex behavior):

cd codex-agent-kernel
cargo run -p codex-kernel-cli -- experiment wrapper-complete

It reports the disagreement as wrapper_complete_process_running.

A narrow invariant that seems sufficient for this failure mode is: if unified-exec reports ProcessStatus::Alive / a live process_id, wrapper/code-mode must not represent the logical command as completed, and the continuation handle must remain model-visible.

Full RCA / reproducible research artifact: https://github.com/byte271/codex/blob/main/codex-agent-kernel/docs/UPSTREAM.md

This is issue evidence only, not an external PR proposal.