Claude Code and Codex Devs

Stop Trusting
the Exit Code

Run Codex or Claude Code as a subprocess and get a verified answer back, not a hang, a false success, or a silent switch to API billing.

Codex and Claude Code lie about success. Get the flags wrong and they hang, or bill your API key.

The external problem

You spawn `codex exec` from a script on Windows and it hangs forever because stdin was never closed, or `claude -p` exits 0 while `git status` shows nothing actually changed.

The internal problem

You stop trusting your own automation. Every headless run becomes a run you have to babysit and re-check by hand, which defeats the point of scripting it.

What's at stake

Keep guessing at flags and you either burn real API spend you meant to avoid, or ship a report that says “done” over work that never happened.

Five steps. Each one closes a failure mode this skill has actually hit.

Run the whole flow before your first scripted call, or jump straight to the step where your last run broke.

01

Lock In the Rules

Close stdin on every Codex call so it can’t hang on an unwritten pipe, own the clock since neither CLI enforces a timeout, and set sandbox, approval, and working directory explicitly instead of inheriting global config.

02

Choose the Call Shape

Pick a quick read-only question or a long write-capable run, for either `codex exec` or `claude -p`, down to the specific flags each shape needs, from `--sandbox workspace-write` to `--permission-mode acceptEdits`.

03

Run Under a Timeout

Foreground short calls with the Bash tool’s own timeout, or use `run_in_background` for anything that could pass ten minutes, since the harness notifies on completion instead of making you poll.

04

Verify Against Ground Truth

Read the answer from `--json` output or the `.result` field, never from an exit code, since both CLIs are effectively 0 or 1 and Codex has exited 0 on Ctrl+C. Then check git status, file existence, or a real test run before trusting a claimed success.

05

Retrieve It Later

If the run outlives the conversation, the full transcript is still on disk (Codex under `~/.codex/sessions/`, Claude under `~/.claude/projects/`). Read it through the `estack-read-agent-history` skill, never by raw-parsing the `.jsonl`.

Flags, exit codes, and traps, per CLI.

Every claim carries a source URL: official docs for both CLIs, plus the GitHub issues where a behavior is undocumented and had to be reproduced firsthand, like the Windows stdin hang and Codex exiting 0 on Ctrl+C.

Reference

Codex Exec

Flags

FlagWhat it does
--cd, -C <path>Workspace root for the run
`--sandbox, -s <read-only\workspace-write\danger-full-access>`Sandbox policy. Default with no flags: read-only
`--ask-for-approval, -a <untrusted\on-request\never>`Approval policy. Use never headless
--dangerously-bypass-approvals-and-sandbox (--yolo)Full bypass — "only use inside an isolated runner"
--full-autoDeprecated; use explicit --sandbox instead
-c, --config key=valueInline config override, repeatable (e.g. -c sandbox_workspace_write.network_access=true)
--ignore-user-configSkip $CODEX_HOME/config.toml entirely — use when the global config must not interfere
--jsonstdout becomes a JSONL event stream (see below)
--model, -m <id>Model override
-o, --output-last-message <path>Write final message to a file
--output-schema <path>Enforce final response against a JSON Schema file
--ephemeralDon't persist a session rollout file
--skip-git-repo-checkAllow running outside a git repo (required otherwise)
codex exec resume --last / resume <SESSION_ID>Continue a prior exec session

Prompt input: positional arg, - for stdin-as-prompt, or pipe + positional arg (arg = instruction, piped content = context).

Output

  • Plain mode: progress → stderr, final agent message → stdout (only that).
  • --json mode: JSONL events on stdout. Types: thread.started (has thread_id — this is the SESSION_ID for resume), turn.started, item.started/item.updated/item.completed (each with item: {id, type, status, ...}), turn.completed (has usage token counts), turn.failed, error.
  • Final answer = the item.completed event where item.type == "agent_message" (its text field).
  • Field-level shapes beyond the ones above aren't fully documented — inspect one real codex exec --json "ls" < /dev/null run before hard-coding a parser.

Exit codes — don't trust them

No official exit-code table exists, and Codex has exited 0 on Ctrl+C/SIGINT ([#4721](https://github.com/openai/codex/issues/4721)). Verify success via turn.completed in the --json stream or via the -o file's existence + content, then verify claimed *effects* against git/filesystem ground truth.

Sandbox semantics

  • read-only: no writes. workspace-write: writes inside workspace root only, network blocked by default — enable per-run with -c sandbox_workspace_write.network_access=true. danger-full-access: no boundaries.
  • Under -a never, a sandbox-blocked action fails back to the model as a tool-call error — no hang, no prompt.
  • Non-zero exit from a tool the model runs can hide that tool's output from the model ([#1367](https://github.com/openai/codex/issues/1367)) — for linters/tests where non-zero means "findings", have the prompt tell Codex to append || true.
  • Sandboxed "build/tests pass" is not trustworthy when the sandbox network posture differs from the real environment (e.g. a build failing at a font fetch before typechecking ever ran).
  • MCP tool calls in exec mode get auto-cancelled (stdin closed → approval prompt reads EOF as reject); only the full bypass flag works around it — avoid designs that need MCP tools inside headless Codex ([#24135](https://github.com/openai/codex/issues/24135)).

Auth (subscription, no API keys)

  • codex login = browser OAuth against the ChatGPT plan; credentials at ~/.codex/auth.json (treat as a password). Tokens auto-refresh during use; mid-run expiry behavior is undocumented.
  • Setting OPENAI_API_KEY or CODEX_API_KEY switches billing to API rates. Precedence when both auths exist is buggy ([#3286](https://github.com/openai/codex/issues/3286)) — just never set them.

Timeouts and process management

  • No whole-run timeout, and no per-tool-call timeout inside a turn. A runaway shell command inside Codex can hang the turn indefinitely and silently drop turn state. Enforce the deadline from the caller: Bash-tool timeout foreground, or run_in_background for long runs.
  • If a run must be killed: kill the process tree (taskkill /T /F /PID <pid> on Windows), then read the session rollout for partial work.
  • Rollouts: ~/.codex/sessions/YYYY/MM/DD/rollout-<local-timestamp>-<uuid>.jsonl, written incrementally (partial state generally survives a kill; no fsync guarantee documented). --ephemeral or history.persistence = "none" disables. Resume replays history but does not resurrect an in-flight tool call.

Windows specifics

  • The stdin hang (SKILL.md rule 1) is the big one — reproduced with PowerShell spawned by Claude Code, exactly this use case ([#20919](https://github.com/openai/codex/issues/20919)). PowerShell has no < redirection operator, so run Codex calls through Git Bash (< /dev/null) or cmd (< NUL).
  • Two sandbox implementations via [windows] sandbox = "elevated" | "unelevated" in config.toml; unelevated (the non-admin fallback) has documented weaker network isolation ([source](https://learn.chatgpt.com/docs/windows/windows-sandbox)).
  • Sandbox-setup helper can crash with STATUS_DLL_INIT_FAILED (0xC0000142) on specific directories (stale sandbox-user SIDs from old versions) — every command in the run fails instantly with that status. Check ~/.codex/.sandbox/sandbox.<date>.log; often transient.

Use it now. It's free.

Works on all platforms. Pick yours and get set up in under a minute.

Download & upload in 60 seconds

One click downloads the zip and opens Claude.ai.

01

Click below — the zip downloads and Claude.ai opens.

02

Click the + button in the skills column on the left.

03

Select Create a skill.

04

Select Upload a skill and upload the zip.

05

Start your cli delegation session by running /estack-drive-cli-agent.

Drive CLI Agent ships in E-Stack, a set of 20 free skills installed by one command. See the whole stack.