GrepBoard/Manuals
51 manuals ·

Run codex exec non-interactively in CI

·Codex CLI 0.163.0 · Ubuntu 24.04 · Node 24 · only the parts that work without login were tested · 2026-10·9 steps

Defaults and flags for codex exec in CI: sandbox and approval, --json, -o, --output-schema, stdin waits, CI auth, and the MCP tool-call cancellation limit.

codex exec runs one task with no interactive screen and exits. Check the defaults and flags on the installed version before putting it in CI. What the sandbox and approval values mean is covered in Fix Codex CLI approval policy and sandbox settings; this page only covers what CI needs.

[1] Check the version and flags.

console
$ codex --versioncodex-cli 0.163.0$ codex exec --help | grep -E '^\s+(-s|-o|--json|--output-schema|--ephemeral|--skip-git-repo-check|--ignore-user-config)'  -s, --sandbox <SANDBOX_MODE>      --dangerously-bypass-approvals-and-sandbox      --skip-git-repo-check      --ephemeral      --ignore-user-config      --output-schema <FILE>      --json  -o, --output-last-message <FILE>

[2] The default is a read-only sandbox with no approval prompts. The banner printed at startup shows it, and the official docs say: "By default, codex exec runs in a read-only sandbox."

output
approval: neversandbox: read-only

If the task edits files, add -s workspace-write. The banner then reads sandbox: workspace-write [workdir, /tmp, $TMPDIR].

[3] Outside a Git repository, it refuses to start. A CI checkout is normally inside one; add --skip-git-repo-check only when it is not.

console
$ codex exec "hi"Not inside a trusted directory and --skip-git-repo-check was not specified.

[4] Close stdin. Even with the prompt as an argument, piped stdin is appended as a <stdin> block (per the help text), and in a shell without a terminal, a run with stdin left open sat waiting for input. If you have no input to pass, add </dev/null.

console
$ codex exec --skip-git-repo-check "hi" </dev/nullReading additional input from stdin...

[5] Three ways to get the result.

  • Default: progress goes to stderr, and only the final message goes to stdout.
  • -o FILE (--output-last-message): writes the final message to a file.
  • --json: stdout becomes a JSON Lines event stream. The docs list the event types thread.started, turn.started, turn.completed, turn.failed, item.*, and error.
console
$ codex exec --json --skip-git-repo-check "hi" </dev/null 2>/dev/null | head -2{"type":"thread.started","thread_id":"…"}{"type":"turn.started"}

To fix the shape of the final answer, pass a JSON Schema file with --output-schema schema.json. A missing file stops the run before it starts.

output
Failed to read output schema file nope.json: No such file or directory (os error 2)

[6] Add --ephemeral so CI does not keep session files. Add --ignore-user-config to keep your own config.toml from affecting the run; auth is still read from CODEX_HOME. --strict-config turns misspelled config keys into failures.

[7] Per the official docs there are two ways to authenticate. Without login, the run retried the connection up to five times on 401 Unauthorized: Missing bearer or basic authentication in header and ended with exit code 101. codex login status prints Not logged in and exits 1.

  • API key: pass it inline for a single run, CODEX_API_KEY=<key> codex exec --json "…". The docs say not to set OPENAI_API_KEY or CODEX_API_KEY as a job-level environment variable.
  • Cached ChatGPT login (~/.codex/auth.json): treat it like a password. The docs advise against it for public or open-source repositories.

On GitHub Actions the docs point to openai/codex-action instead of installing the CLI yourself. Their example gives the Codex job only contents: read, uploads the diff as a patch artifact, and a separate job opens the PR without receiving the API key.

[8] To continue a session, use codex exec resume --last "…" or codex exec resume <SESSION_ID>.

[9] Tasks that call MCP tools can get stuck. openai/codex #24135 (filed 2026-05, still open when checked) reports that under codex exec -s read-only an MCP tool call is cancelled with user cancelled MCP tool call. The issue has no supported way to allow it while keeping the sandbox; --dangerously-bypass-approvals-and-sandbox makes the call work but turns the sandbox off too. One comment (CLI 0.154.0) says per-tool approval_mode = "approve" worked; this page did not verify that. Use the bypass flag only on a runner that is isolated from the outside.

#codex#ci#headless#sandbox