Skip to content

Guardian-approved unified exec drops canonical process identity and hides a live background wait #34115

Open

Description

What version of Codex CLI is running?

codex-cli 0.144.23

The observed build is based on rust-v0.145.0-alpha.23 with local patches.

The locally modified source was compared with its upstream base before preparing this report. The local changes do not modify forced-rm classification, exec-policy prompt generation, Guardian routing conditions, the command_execution_started dedupe state, canonical unified-exec process identity, terminal-interaction correlation, or stale-poll filtering.

What subscription do you have?

ChatGPT Pro 20x

Which model were you using?

gpt-5.6-sol

What platform is your computer?

linux amd64/aarch64

What terminal emulator and version are you using, if applicable?

alacritty-0.16.1, tmux-3.4

What issue are you seeing?

When automatic Guardian review approves a unified-exec command that remains alive after the initial yield, the process continues running but the TUI can lose the command's canonical startup identity.

The Working bar then retains a reasoning-derived title such as:

Polling underlying system

The expected live-process title is:

Waiting for background terminal

The process itself is not necessarily hung or terminated. The failure is that app-server suppresses the canonical command start carrying the real process_id and UnifiedExecStartup source after a Guardian synthetic placeholder has already used the same item ID.

The TUI therefore cannot correlate later empty terminal polls with the live process. The valid poll looks unknown and is discarded by the stale-poll guard, leaving the reasoning-derived Working title visible.

This became substantially easier to observe after be39aab9f45fc76705590b063bc2480827571561 expanded forced-rm detection. Long build, test, and installation commands frequently contain a forced cleanup such as rm -rf "$temporary_directory", so many more complete tool calls now enter automatic review.

The expanded review frequency did not create the lifecycle defect. It increased the number of Guardian-approved long-running commands and exposed the already-latent synthetic-placeholder versus canonical-start collision.

What steps can reproduce the bug?

  • Enable automatic approval review with approvals_reviewer = "auto_review" and an approval policy that routes prompts through the reviewer.
  • Run a compound shell or unified-exec command that requires approval and remains alive after the initial tool yield.
  • A forced cleanup inside the compound command is one reliable way to produce the approval request on versions containing be39aab9f45fc76705590b063bc2480827571561.
  • Let Guardian approve the action.
  • Allow the real process to continue running in the background.
  • Poll the process with an empty write_stdin call.
  • Allow a reasoning summary title to arrive while the process is still live.
  • Observe that the process remains alive but the TUI does not show the correlated background wait and can retain the reasoning-derived title.

The minimal event sequence is:

GuardianAssessment(InProgress)
synthetic CommandExecution start
item/autoApprovalReview/started
item/autoApprovalReview/completed: approved
real unified-exec process starts
canonical CommandExecution start uses the same item ID
app-server suppresses the canonical start as a duplicate
empty TerminalInteraction poll carries the real process ID
TUI cannot correlate the process ID and ignores the poll
reasoning-derived Working title remains visible

The synthetic start has placeholder metadata:

id = target_item_id
source = Agent
process_id = None
status = InProgress

The later canonical start has the information required by the TUI:

id = target_item_id
source = UnifiedExecStartup
process_id = Some(real_process_id)
status = InProgress

App-server currently records only whether the item ID has started. Once the synthetic placeholder inserts the ID into turn_summary.command_execution_started, the later canonical start is treated as a complete duplicate rather than as a metadata upgrade.

The TUI calls track_unified_exec_process_begin only for UnifiedExecStartup. Seeing only the synthetic Agent item means it never establishes the real process mapping.

What is the expected behavior?

  • Guardian review ordering should remain intact, with the command item visible before the review starts.
  • The synthetic placeholder and canonical start should represent one logical command rather than two transcript items.
  • A canonical start following a synthetic placeholder should upgrade or upsert the same item with UnifiedExecStartup and the real process_id.
  • Empty polls from a live process should correlate with the command and show Waiting for background terminal.
  • Reasoning titles should not replace a valid live background-wait state.
  • Command completion should clear the process and wait state exactly once.
  • Guardian denial should complete the synthetic item without requiring a canonical process start.
  • Ordinary commands without Guardian review should preserve their existing lifecycle.
  • Empty polls from processes that have already exited should continue to be ignored.

Additional information

Existing Issue Search

The open and closed issue and pull-request search did not find an existing report for this exact Guardian synthetic-placeholder to canonical-start metadata loss.

Related reports such as #12033, #14303, #14314, and #22957 describe stale or repeated background waiting, generally after a command appears to have completed. This report is different because the process is still live and the canonical startup identity is lost before the empty poll reaches TUI correlation.

Relevant Upstream Commits

7532f34699fca93fc52cbb1ae77d153a70488382

  • Date: 2026-01-14.
  • Subject: fix: drop double waiting header in TUI (#9145).
  • Relevance: the TUI background-wait surface relies on unified-exec process correlation.
  • Ownership: presentation and wait-state handling, not Guardian approval frequency.

bd30bad96f92de4d6b7f2429ddfc322937bc670a

  • Date: 2026-04-06.
  • Subject: fix(guardian): fix ordering of guardian events (#16462).
  • Relevance: creates the necessary pre-review synthetic command item with placeholder source and process metadata.
  • Ownership: first half of the lifecycle collision.

e43a2e297fe72285890e0f04f9f1aece843c2338

  • Date: 2026-05-19.
  • Subject: Fix stale background terminal poll events (#23231).
  • Relevance: correctly ignores empty polls for unknown or exited processes.
  • Interaction: a live process also appears unknown when its canonical startup metadata was suppressed.

aa94ea139744f0a9dd76343805cc96649d9f2658

  • Date: 2026-07-06.
  • Subject: chore(approvals) consolidate guardian calls for shell tools (#31267).
  • Relevance: centralizes routing of an already-created shell approval request through the neutral approval action.
  • Ownership: Guardian routing after approval generation, not forced-rm classification.

cca16a10878202cb2f6e9666b6b4330329ea7e65

  • Date: 2026-07-06.
  • Subject: feat(core): emit canonical command execution items (#31297).
  • Relevance: emits the canonical command start with the real unified-exec source and process identity.
  • Ownership: second half of the lifecycle collision when the same item ID was already occupied by the Guardian placeholder.

be39aab9f45fc76705590b063bc2480827571561

  • Date: 2026-07-16.
  • Subject: Strengthen forced rm command detection (#33464).
  • Relevance: expanded deterministic forced-rm recognition across control flow, substitutions, pipelines, traps, nested shells, sudo, env, executable paths, and force-option placement.
  • Version boundary: absent from rust-v0.145.0-alpha.13 and present in rust-v0.145.0-alpha.20, rust-v0.145.0-alpha.22, and rust-v0.145.0-alpha.23.
  • Discovery relationship: increased Guardian Review frequency and made the lifecycle collision easy to reproduce, but did not create the item lifecycle defect.

Trigger Classification Detail

The current deterministic rule is not a raw substring check for rm.

It recognizes a literal rm invocation carrying -f or --force, including combined, separated, reordered, wrapped, or nested forms.

Examples now requiring approval include:

/bin/rm -fr target
rm -r -f target
rm --force target
rm target -f
sudo rm -rf target
env TARGET=target rm -rf target
if test -d target; then rm -rf target; fi
echo "$(rm -rf target)"
trap 'rm -rf target' EXIT

The rule does not classify these as forced rm:

rm -r target
echo 'rm -rf target'
cmd=rm; $cmd -rf target

A plain rm target can still be reviewed if the main model explicitly requests require_escalated or another policy rule requires approval, but that is separate from the deterministic forced-rm expansion.

Suggested Fix Invariant

No prior start: emit the canonical start.
Prior canonical start: suppress the true duplicate.
Prior synthetic placeholder: update or upsert source and process_id without duplicating the logical command.

The synthetic start should not be removed because it preserves Guardian review ordering.

The app-server started state should distinguish a synthetic placeholder from a canonical command rather than represent both with one boolean item-ID set.

Suggested Regression Coverage

  • Guardian approved plus long-running unified exec plus empty live poll.
  • Guardian approved plus short-lived command completion.
  • Guardian denied without a real process start.
  • Non-Guardian and manually approved command lifecycles.
  • Stale empty poll after process exit.
  • Reasoning deltas while a valid background wait is active.
  • Two concurrent Guardian-approved commands with distinct process IDs.
  • One logical command transcript item across placeholder-to-canonical upgrade.

Local Modification Check

The issue was reproduced on a locally modified Codex CLI build.

The local modifications were checked before this report was prepared and do not touch the relevant forced-rm classifier, exec-policy approval generation, Guardian routing condition, synthetic-start dedupe state, canonical process metadata, terminal-interaction correlation, or stale-poll guard.

No private patch commit names or unpublished implementation details are included in this report.

Authorship Note

This issue report and its source-history analysis were prepared with assistance from Codex CLI.

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    app-serverIssues involving app server protocol or interfaces
    on Jul 19, 2026
  2. qb20nh commented on Sep 9, 2026

    @qb20nh

    On Linux with codex-cli 0.153.4, the user reported an empty /ps while an automatically approved native exec job continued downloading files. The tool returned a live session ID, and OS and filesystem checks confirmed ongoing work.

    This adds evidence of missing process visibility. The synthetic-start/canonical-start collision described in this issue remains unconfirmed for this case. Code Mode was also involved.

    The environment was:

    • CLI: codex-cli 0.153.4, verified with both codex --version and /proc/<codex-pid>/exe --version.
    • Release check: On 2026-09-09, GitHub's releases/latest endpoint returned rust-v0.153.4, published 2026-09-04. This report makes no claim about main or alpha builds.
    • Platform: Linux 7.2.2-1-cachyos, x86_64; codex resume in Zed's integrated terminal, with a fish parent shell.
    • Approval: approvals_reviewer = "auto_review". The command requested sandbox_permissions: "require_escalated" for mounted-disk/network access.
    • Execution: functions.exec called native tools.exec_command; a separate Code Mode cell polled through tools.write_stdin.
    • Plugins: context-mode MCP tools were used for inspection. The downloader was launched through native exec, without ctx_execute or an MCP background-process option.

    The foreground Node command downloaded four public video sources to a mounted disk, printed progress, and then checked integrity. It used no shell &, nohup, explicit setsid, tmux, or daemonization. Its stdin was /dev/null; stdout and stderr were pipes.

    The launch used this form, with the script path and approval wording shortened:

    // Inside functions.exec
    text(await tools.exec_command({
      cmd: "node /path/to/project/work/evidence/download-4k-sources.cjs",
      yield_time_ms: 1000,
      max_output_tokens: 1000,
      sandbox_permissions: "require_escalated",
      justification: "Resume source downloads on the mounted data disk with free-space reserves."
    }));

    Automatic review approved the command. Native exec returned initial download/resume output, session ID 53915, and no exit code, indicating that the process remained running.

    Polling used:

    // Inside a yielded functions.exec cell
    await tools.write_stdin({
      session_id: 53915,
      chars: "",
      yield_time_ms: 30000,
      max_output_tokens: 6000
    });

    The user then reported that /ps showed no background terminals. No screenshot or raw TUI event capture is attached, and a second /ps observation was not requested at the later timestamp below.

    An OS process-table check showed:

    PID     PPID    SID     TTY    COMMAND
    445937  18952   445937  ?      node /path/to/project/work/evidence/download-4k-sources.cjs
    18952   14387   14387   pts/0  codex resume
    

    Tool session ID 53915 and OS PID 445937 are distinct identifiers. The downloader was a direct child of the active Codex process.

    Filesystem checks confirmed ongoing writes to the nature master, whose expected size was 25,453,765,853 bytes:

    Time on 2026-09-09 (UTC) Downloaded bytes
    Approximately 09:17:54 1,281,815,820
    10:41:22 24,530,911,820

    All four .part files were incomplete at the first check. At the second, the same PID and parent relationship remained after more than 92 minutes of runtime. The combined verification report was absent at both checks. The job later completed and produced its integrity report before this comment was first posted.

    The empty /ps display left the user unsure whether downloading had finished and whether it was safe to exit Codex. OS checks were needed to establish that Codex still owned active work.

    Expected behavior: /ps should show the command and running state of a live native exec job owned by the session, including non-TTY jobs launched through Code Mode and automatic approval. If that execution path is intentionally outside the terminal registry, the UI should make the remaining active work visible.

    The diagnostic limits are:

    • A separate codex doctor --json invocation exited 0 and reported ok for installation, config loading, MCP configuration, and standalone Linux runtime provenance (0.153.4). This is a selected summary with private environment details omitted. It does not verify the active TUI's process registry.
    • This is one observed session. There was no controlled A/B reproduction, plugin-disabled run, or confirmed regression boundary.
    • No CommandExecution start events, source, canonical process_id, item-ID deduplication, or TUI registry updates were captured.
    • /stop and session exit were not tested against these downloads. No termination failure or survival after session exit is claimed.
    • A completed wrapper cell alone does not establish process completion. Native exec returned a live session ID; there is no claim that write_stdin falsely reported completion.

    Before the original comment, I reviewed docs/contributing.md, the CLI bug template, and the issue bodies and all 27 existing comments across #34115, #12033, #14303, #14314, #22957, #38093, #16373, #13708, #15461, #22935, and #38069. The relevant distinctions are:

    A useful next step would be to trace the approved launch through canonical native-exec startup, Code Mode, and /ps registration, correlating session 53915 with PID 445937. A bounded heartbeat command could compare automatic versus ordinary approval, Code Mode versus direct execution, and non-TTY versus PTY. These comparisons have not been performed.

    Prepared with Codex assistance from the user's TUI observation and live local diagnostics. Private paths are redacted; no full conversation or credentials are included.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIapp-serverIssues involving app server protocol or interfacesbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions