Skip to content

Non-Interactive Mode with codex exec: Running Codex in Scripts and CI

📚 Series Navigation: The previous article [27 Automation and CI/CD] provided an overview of running Codex automatically in workflows—what tasks to automate and how to set them up in CI. This article focuses on the core command: codex exec, which represents the "non-interactive mode" that accepts a prompt, runs the task, and exits without opening a terminal interface. The next article [29 Slack / Linear and SDK Integrations] covers connecting it to team chats and ticket systems.

Everyone, today let's talk about a command you will eventually need—codex exec.

I briefly mentioned it in Article 08 on the CLI, comparing it to "ordering takeout": you place an order (a prompt), and the kitchen prepares and delivers it to your terminal, without you having to watch it. This article opens the kitchen door, showing how the takeout is made, packaged, and piped into automated systems.

Why focus on it? Because the interactive mode (running codex to open the full-screen terminal UI) has a limitation: it assumes a human is sitting in front of the screen—monitoring discussions, hitting enter to approve commands, and inspecting diffs. But if you want to "review yesterday's commits automatically every morning," "open a bugfix PR when CI runs fail," or "pipe build errors to Codex to summarize root causes"—there is no human in these loops. The interactive TUI would hang waiting for approval. codex exec is built for these unattended loops.

Simply put, mastering codex exec is the bridge between using Codex as an interactive tool and integrating Codex as a component in your automated workflows.

By reading this article, you will get:

  • An explanation of the difference between codex exec and interactive mode, and the unattended use cases it addresses
  • Understanding its output design: status logs go to stderr, while the final message goes to stdout—making pipe redirections clean
  • How to pair --json (machine-readable event stream) and -o / --output-last-message (writing the final output to a file) to cover both machine logs and human reports in CI
  • How sandbox permissions default to read-only in non-interactive mode, requiring explicit switches to write changes, along with a sandbox level table
  • When to pipe data using "prompt + stdin" vs. passing entire prompts via codex exec -
  • A minimal hands-on workflow to test runs, capture outputs, and wrap the command in batch scripts

⚠️ Specific subcommands, options, and default behaviors mentioned below are subject to the official Codex documentation (Non-Interactive Mode); models and versions are subject to your active installation.


01 Understand First: What Problem Does Non-Interactive Mode Solve?

The bottom line: codex exec is designed for unattended workflows—running inside scripts, cron jobs, and CI/CD pipelines where no user is present to approve prompts, meaning tasks must run to completion and output must be readable by subsequent scripts.

Recall our previous articles—we worked almost exclusively in "interactive mode": running codex to open the full-screen terminal interface, passing prompts, watching files change, and clicking confirm. This works for 90% of tasks, provided you are present.

But three scenarios make this impractical.

First: highly repetitive tasks. For example, "generating release notes for yesterday's ten commits daily." This is simple but tedious—you don't want to open Codex manually, paste commits, wait for output, and copy the text every morning.

Second: environments lacking terminal displays. CI runners, cloud servers, and Docker containers do not have interactive displays, meaning the full-screen TUI (Terminal User Interface) cannot initialize.

Third: passing Codex output to subsequent scripts. For example, asking Codex to format error logs into a Markdown table and piping that text directly to gh pr comment to post on a PR. This requires Codex to output clean text like a standard CLI utility, rather than rendering interactive UI layouts.

Analogy: A vending machine vs. a serviced counter. Interactive mode is a counter with a server—you ask for an item, they prepare it, ask if you want sugar, and hand it to you. codex exec is a vending machine—you press a button (the command), the machine processes it inside, and the product drops into the tray (stdout), without servers, questions, or your presence needed. Vending machines excel at unattended services: running at 3 AM in pipelines on multiple machines without human intervention.

Typical use cases for codex exec:

  • "Extracting failing logs on CI failures to write a bugfix PR"—running autonomously in pipelines;
  • "Generating release notes for the latest commits and writing them to a file"—automated in a script;
  • "Piping error logs to Codex to retrieve root causes and troubleshooting steps"—run in a single line, saving you from copying and pasting into chat boxes.

💡 Summary in one sentence: codex exec is built for unattended workflows (like a vending machine)—managing repetitive tasks, running in headless environments, and piping output to downstream utilities without requiring your presence.


02 Getting Started: The Minimal Command

Do not be intimidated by "non-interactive" or "CI" terms; codex exec is simple to run—type codex exec followed by your prompt, press enter, and it executes.

bash
codex exec "Summarize the repository structure and list 5 areas of concern"

It scans the active directory, drafts a plan, prints logs to the console, outputs the summary, and exits—without launching the TUI or waiting for input. This is non-interactive behavior: it executes and exits, avoiding back-and-forth chat.

Analogy: Sending a task email vs. calling on the phone. Interactive mode is a phone call—allowing back-and-forth remarks. codex exec is sending an email—you describe the requirements in a message (the prompt) and send it, and the recipient completes it and replies (the final output). You cannot adjust instructions mid-run, so write the prompt thoroughly.

Useful variants:

bash
# Short alias codex e, equivalent to codex exec
codex e "Explain what this project does"
bash
# Run with a specific model (replace with active models on your account)
codex exec -m gpt-5.5 "Review uncommitted changes and list potential bugs"
bash
# Use --ephemeral to avoid saving the session to history
codex exec --ephemeral "Analyze the repo and suggest next steps"

Here is a major difference from interactive mode: when running interactively, Codex pauses to ask for approval before executing commands; codex exec is designed to run unattended, so it does not prompt for approvals mid-run. This allows unattended execution, but write actions are governed by your startup sandbox level. We cover this in Section 04; keep this lack of prompts in mind.

⚠️ Git Repository Prerequisite: Codex requires commands to execute inside a Git repository (bypassable with --skip-git-repo-check) to prevent irreversible modifications on untracked folders. Running codex exec outside a Git directory throws an error. When running in temporary paths, use --skip-git-repo-check to bypass this check—but the docs warn: only run this if you verify the environment is secure.

💡 Summary in one sentence: codex exec "prompt" is the basic non-interactive run (aliased to codex e), acting like an email task that exits on completion without prompting for approvals; and it requires running inside a Git directory unless bypassed with --skip-git-repo-check.


03 The Output Design: Status Logs to stderr, Final Message to stdout

This section covers the most important design detail—allowing codex exec to pipe clean text to downstream utilities.

A common issue: Codex prints status logs as it executes—what it is checking, what commands it runs, and what files it edits. But you typically only want the final summary. If both outputs were combined in a single stream, writing output to a file would clutter it with execution logs, making downstream parsing difficult.

Codex resolves this by splitting outputs into separate channels:

Status logs write to stderr (standard error), while the final message writes to stdout (standard output). These are separate UNIX streams, and pipes | and redirects > capture stdout only. This split makes capturing the clean final output simple.

Analogy: Construction noise vs. the finished house. Building a house involves noise—hammering, drilling, and mixing concrete (these represent stderr; you hear them, but they are not the product). The finished house is the deliverable (representing stdout). Keeping construction noise out of the front door ensures you receive the clean house without dust and noise.

How this helps: the command below prints the final message to the terminal while saving it to a file (via tee), while status logs print to the terminal but do not write to the file:

bash
codex exec "Generate release notes for the last 10 commits" | tee release-notes.md

Expected: The terminal displays Codex's execution status (via stderr) followed by the final release notes; meanwhile, release-notes.md contains only the clean release notes, free of status logs.

Understanding this split makes command combinations simple:

TaskCommandMechanism
Save final message onlycodex exec "..." > out.md> redirects stdout, leaving status logs (stderr) on the terminal
Save and inspect final messagecodex exec "..." | tee out.mdtee duplicates stdout to the file and terminal
Pipe final message to clipboardcodex exec "..." | pbcopyDownstream utility receives only the clean stdout message
Capture both streams separatelycodex exec "..." > out.md 2> log.txt2> redirects stderr (status logs) to a separate file

I made a mistake in my first script: attempting to save the log, I used 2>&1 to merge stderr into stdout, which filled my file with "thinking..." status lines, breaking downstream JSON parsing. Removing 2>&1 cleaned up the file immediately. Let the default stream division handle it.

💡 Summary in one sentence: codex exec writes status logs to stderr and the final output to stdout (like separating construction noise from the finished house), allowing > and pipes to capture clean results; avoid using 2>&1 which merges status noise into the output file.


04 Permissions and Safety: Sandbox Defaults to read-only

We covered output divisions; now let's discuss permissions: what can codex exec do when running unattended?

The rules default to safety: codex exec runs in a read-only sandbox by default—it can read and analyze files, but does not modify code or run commands with side effects. To let it modify files, you must explicitly configure the sandbox level.

Why this default? Because it runs unattended—without user prompts. If it had write access by default, an instruction misunderstanding could modify files unchecked. The safety rule: grant the minimum sandbox level required to complete the task.

Analogy: Handing house keys to a service technician while you are away. You do not give them the master key and safe combination. By default, you allow access only to the living room (read-only); if they must fix a pipe, you explicitly grant access to the kitchen or bathroom (workspace-write, restricted to the workspace); unless it is a isolated, secure area, you never hand over a master key allowing access to the entire building (danger-full-access). Restricting permissions ensures unattended tasks run safely.

Configure permissions using --sandbox (aliased to -s):

Sandbox LevelPermissionsUse Case
read-only (exec default)Reads files, blocks writes and commands with side effectsCode reviews, summaries, audits—where only text output is needed
workspace-writeReads and writes files inside the workspace directoryLet it fix tests, apply bugfixes, or write files in the project path
danger-full-accessUnrestricted access to the runner systemOnly in isolated environments (dedicated CI runners or containers)

In practice:

bash
# Read-only default: reviews code and outputs reports without touching files
codex exec "Review changes for potential bugs"
bash
# Workspace write access: allows it to write files inside the project path
codex exec --sandbox workspace-write "Fix failing test cases"
bash
# Unrestricted access: isolated environments only
codex exec --sandbox danger-full-access "<CI runner task>"

Key details to keep in mind:

1. Avoid using --full-auto. Legacy scripts might use codex exec --full-auto, which is now an obsolete compatibility flag that raises a warning. The docs state: use --sandbox workspace-write instead to define intent clearly.

2. Restrict danger-full-access to isolated environments. This level grants broad system access; the docs state: "Only run in controlled environments (like isolated CI runners or containers)." Running this on your main development machine on active projects is risky.

3. Run clean builds using configuration bypasses. Use --ignore-user-config to skip loading $CODEX_HOME/config.toml (avoiding local user configuration overrides); use --ignore-rules to skip checking user or repository .rules files. These are useful in automated environments to ensure consistent behavior across runners.

I ran into a permissions issue on my first CI setup: assuming codex exec inherited my interactive CLI configurations (where I had write permissions active), I couldn't figure out why it completed runs without modifying files. Non-interactive mode defaults to read-only, regardless of your interactive profile. Adding --sandbox workspace-write resolved the issue.

💡 Summary in one sentence: codex exec defaults to a read-only sandbox (restricting write actions); use --sandbox workspace-write to allow file modifications inside the project path, and restrict danger-full-access to isolated runners; legacy --full-auto is obsolete, and use --ignore-user-config / --ignore-rules for clean automation runs.


05 Structuring Output: Pairing --json and -o

Section 03 covered keeping status logs out of outputs, but we must also ensure outputs are readable by downstream scripts. Parsing natural language summaries in scripts is fragile.

Codex provides two complementary options to help structure outputs:

1. --json: outputs the entire run as a machine-readable event stream. Using --json (or the alias --experimental-json) changes stdout from a text message to a JSON Lines (JSONL, one JSON object per line) stream. Codex prints a JSON object for every status update during execution.

2. -o / --output-last-message: writes the final message to a file. If you only want the final summary written to a file for subsequent steps, use -o <file_path> (or --output-last-message):

bash
codex exec "Compile project metadata" -o ./summary.md

Note: -o writes the final message to the file while printing it to stdout—allowing you to save it and pipe it downstream simultaneously.

Choosing between the options:

GoalOptionFormat
Read steps / check success / track token usage in scripts--jsonstdout turns into a JSONL event stream
Save the final summary message to a file-o <path>Final message is saved, and still printed to stdout
Both logs and final summaries in CICombine --json and -oPipe JSONL to scripts, and write summary to a file

Combining --json and --output-last-message is the recommended setup for CI—capturing machine-readable logs while saving a clean Markdown report.

I use this in a daily code audit script: the --json stream is piped to a script that checks status variables to decide if it should send alerts; meanwhile, the -o file captures the Markdown summary to post to a team Slack channel. One run satisfies both scripts.

To enforce specific JSON outputs, use --output-schema with a target JSON Schema file, which forces the model to structure the final message according to defined keys.

💡 Summary in one sentence: --json structures stdout as a JSONL event stream for script parsing, while -o / --output-last-message writes the final message to a file while keeping stdout intact; combine both in CI pipelines to capture logs and reports.


06 piping Input: Feeding Data to codex exec via stdin

We covered managing codex exec outputs; now let's look at inputs—how to pipe data from other commands into codex exec. Standard input (stdin) piping is a powerful integration tool in command-line workflows.

This is useful when you want to pass data to Codex for processing: build logs on failures, JSON query outputs, or test results. Piping saves you from manual copy-paste loops.

The system supports two piping modes based on where instructions come from: "prompt + stdin" (data is context) vs. codex exec - (entire prompt is passed via stdin).

Analogy: Directing an assistant verbally with papers vs. handing over a written memo. The first mode is giving verbal instructions ("format this text as a table") while handing over a stack of documents—instructions are verbal, documents are context (prompt + stdin). The second mode is handing over a written memo and saying "do what is written here"—both instructions and data are on the paper (codex exec -).

Mode 1: Prompt + stdin (Prompt acts as instruction, stdin acts as context)

Use this when you write the instruction in the command and want to pass a command output as context. The rules:

If stdin is piped to Codex and you specify a prompt argument, Codex treats the prompt as instructions and the stdin stream as context.

Example: piping test failures to Codex to analyze errors:

bash
npm test 2>&1 \
  | codex exec "Analyze the test failures and suggest a minimal fix" \
  | tee test-summary.md

Expected: The npm test output (including stderr via 2>&1) is passed as context to Codex, which processes it based on your prompt, printing the summary to the screen and saving it to test-summary.md. This is much faster than copy-pasting terminal outputs.

Example: analyzing log errors:

bash
tail -n 200 app.log \
  | codex exec "Identify the root cause, list error logs, and suggest troubleshooting steps" \
  > log-triage.md

Mode 2: codex exec - (stdin acts as the entire prompt)

Use this when the entire prompt is generated dynamically by another script—such as reading prompts from files or composing them in shell scripts. If you omit the prompt argument, Codex reads stdin; use the - placeholder to make this explicit:

bash
# Read prompt from a file
cat prompt.txt | codex exec -
bash
# Compose instructions in a shell script and pipe them to Codex
printf "Summarize these logs in 3 points:\n\n%s\n" "$(tail -n 200 app.log)" \
  | codex exec -

This is useful when you store prompts in files or compose them in shell scripts, keeping instructions separated from execution code.

💡 Summary in one sentence: Choose how to pass inputs—use prompt + stdin to pass instructions in the command and data via the pipe (verbal instruction + documents); use codex exec - when the entire prompt is generated dynamically by upstream commands (memo document).


07 Continuing Sessions: codex exec resume

Non-interactive mode supports multi-step workflows. You can ask Codex to analyze code first, and then ask it to write fixes based on the analysis. You can link these steps using resume.

Analogy: A relay race. The first runner completes their leg (the first codex exec run) and passes the baton, and the second runner continues from that point (codex exec resume)—retaining context without restarting from the beginning. Without resume, the second run would require re-feeding the first run's output, consuming tokens and risking lost details.

Example:

bash
# Step 1: Analyze changes for race conditions
codex exec "Check if this change introduces race conditions"

# Step 2: Fix the identified issue in the same session context
codex exec resume --last "Fix the race condition you identified"

--last instructs Codex to continue the most recent session in the active directory. To target a specific past session, pass its session ID:

bash
codex exec resume <SESSION_ID> "Continue the task"

Notes: check all directories using --all; the prompt after resume is optional—you can resume a session to inspect state or pass new instructions.

Note: sessions run with --ephemeral do not save to history, meaning they cannot be resumed. Do not use --ephemeral if you need to chain runs.

💡 Summary in one sentence: codex exec resume --last continues the most recent session in the active directory (relay race), and can target specific sessions by ID; sessions run with --ephemeral cannot be resumed.


08 Hands-on: Testing a Scripted Workflow

Let's run a minimal hands-on workflow inside a Git repository (run git init in a directory if needed) to verify these options.

Prerequisite: Codex CLI installed, running inside a Git repository. The steps do not require external network configurations.

Step 1: Run a basic command

bash
codex exec "Explain what this repository does in one sentence"

Expected: Status logs scroll on the terminal (via stderr), followed by the summary (via stdout), and the command exits back to your terminal prompt. This verifies non-interactive execution.

Step 2: Save output to verify stream separation

bash
codex exec "Explain what this repository does in one sentence" > result.txt

Expected: Status logs still print to the terminal (stderr is not redirected), but checking result.txt reveals only the clean summary sentence, free of status logs. This verifies that > redirects stdout only.

Step 3: Output JSON event streams

bash
codex exec --json "List the file types in this repository"

Expected: The output prints line-by-line JSON blocks (JSONL) starting with {"type":"thread.started",...}, listing actions under item.*, and ending with turn.completed detailing token usage. This confirms machine-readable outputs.

Step 4: Save final message using -o

bash
codex exec "Summarize this project in one sentence" -o last.md

Expected: The summary prints to the terminal (stdout is not blocked), and last.md saves the same sentence. This verifies -o writes to file while preserving stdout.

Step 5: Run a batch script

Use a shell loop to run codex exec on multiple targets (we include --ignore-user-config to ensure clean, consistent runs):

bash
for f in *.md; do
  echo "=== $f ==="
  codex exec --ignore-user-config "Explain what $f covers in one sentence"
done

Expected: The loop runs codex exec on each Markdown file in the folder, printing the filename followed by its summary. This is the basis of unattended batch scripts.

This completes the hands-on verification: running commands, checking streams, parsing JSON, saving files, and running loops. Combine these steps with --sandbox workspace-write to let Codex write changes in your automation pipelines.

💡 Summary in one sentence: The hands-on workflow has five steps—run codex exec → redirect stdout using > to verify streams → check JSON outputs via --json → save reports using -o → run a batch for loop; verifying CLI integrations.


09 Summary

We have explored codex exec in non-interactive mode—enabling command-line integration, stream separation, sandbox settings, JSON event streams, stdin pipes, and session continuations.

Let's review the key options:

TaskCommand / OptionKey Takeaway
Run a non-interactive taskcodex exec "..." (short name codex e)Exits on completion without prompting for approvals
Capture clean final outputcodex exec "..." > out.mdFinal message goes to stdout, status logs to stderr
Allow file writes--sandbox workspace-writeDefaults to read-only; must be explicitly enabled to edit files
Capture event logs--jsonFormats stdout as a JSONL stream for script parsing
Save summary to file-o <path>Saves final message to file while printing to stdout
Pipe data as inputprompt + stdin / codex exec -Match to whether instructions are verbal or written in the stream
Continue sessionscodex exec resume --last "..."Chains runs, preserving context; blocked by --ephemeral

You should now be able to: explain where non-interactive mode is used; capture clean outputs using stdout/stderr divisions; configure sandbox permissions; pair --json and -o in CI; pipe inputs to Codex; and continue sessions using resume.

Remember: codex exec turns Codex from an interactive chat interface into a component you can call from scripts, pipes, and CI configurations.


The next article [29 Slack / Linear and SDK Integrations]—we covered calling Codex from scripts and CI. The next article moves to daily collaboration tools: calling Codex in Slack channels, assigning tickets automatically in Linear, or embedding its capabilities in your applications using the SDK.