# Codex in CI: codex exec and the GitHub Action

> Run Codex in CI with codex exec and openai/codex-action: read-only defaults, API key handling, safety strategies and a two-job pull request review workflow.

Canonical: https://bridge-terminal.com/atlas/blog/codex-exec-github-action-ci-review/
Last updated: 2026-10-31

Published 31 October 2026 · Technical · How-to · 6 min read

## The short version

- codex exec runs Codex without the interactive screen and defaults to a read-only sandbox, so a CI job cannot change files unless you grant it.
- In GitHub Actions, the docs recommend the openai/codex-action over installing and signing in the CLI yourself. Set its sandbox or permission profile explicitly: with neither, the action uses the legacy workspace-write sandbox.
- Never set the API key as a job-level environment variable in workflows that run repository-controlled code; pass it only to the step that needs it.
- Split a pull request review into two jobs: one that runs Codex with only contents: read, and one that holds the write permissions and posts the comment.
- Keep the default write-access trigger rule, and list any extra users or bots explicitly; the wildcard is not supported for bots.

To run Codex in CI, use `codex exec`, the non-interactive command. On GitHub, the docs recommend the `openai/codex-action` wrapper around it. The CLI starts in a read-only sandbox. The action does not: if you set neither `sandbox` nor `permission-profile`, it runs Codex with the legacy `workspace-write` sandbox. So choose the sandbox yourself, split reading from writing, and keep the API key out of any place that repository code can read.

This guide covers the commands, the action's inputs, the safety settings that matter, and a pull request review workflow you can adapt.

## What does codex exec do?

`codex exec` runs Codex once, with no interactive screen, so scripts and CI jobs can call it. Progress streams to `stderr`, and only the final agent message goes to `stdout`, which makes it easy to pipe or redirect.

```bash
codex exec "summarize the repository structure and list the top 5 risky areas"
codex exec "generate release notes for the last 10 commits" | tee release-notes.md
```

Codex expects to run inside a Git repository. `--skip-git-repo-check` overrides that requirement; use it only when you are sure the environment is safe. `--ephemeral` avoids saving session rollout files to disk.

## Sandbox and approvals in non-interactive mode

By default, `codex exec` runs in a read-only sandbox. You widen it explicitly:

```bash
codex exec --sandbox workspace-write "<task>"
codex exec --sandbox danger-full-access "<task>"   # only in a controlled environment
codex exec --approve-for-me "<task>"               # routes approval requests through automatic review
```

Two details matter if you have older scripts. `--full-auto` is no longer supported; replace it with `--sandbox workspace-write`. And `--ignore-user-config` skips `$CODEX_HOME/config.toml`, while `--ignore-rules` skips user and project execpolicy `.rules` files. Both help when you want a CI run that does not depend on whoever configured the runner.

If an enabled MCP server has `required = true` and fails to start, `codex exec` exits with an error rather than carrying on without it.

## How do I get machine-readable output?

Three options, depending on what the next step in your pipeline needs.

**JSON Lines.** `--json` turns `stdout` into an event stream. Event types include `thread.started`, `turn.started`, `turn.completed`, `turn.failed`, `item.*` and `error`.

```bash
codex exec --json "summarize the repo structure" | jq
```

**Final message to a file.** `-o` (or `--output-last-message`) writes the last message to a file and still prints it.

```bash
codex exec "summarize the open TODOs" -o ./out.md
```

**A fixed shape.** `--output-schema` asks for a final response that conforms to a JSON Schema, which suits a script that reads the result.

```bash
codex exec "Extract project metadata" \
  --output-schema ./schema.json \
  -o ./project-metadata.json
```

To continue earlier work, resume a session:

```bash
codex exec "review the change for race conditions"
codex exec resume --last "fix the race conditions you found"
codex exec resume <SESSION_ID> "..."
```

## How should I handle the API key in CI?

By default `codex exec` reuses saved CLI sign-in. For automation, the docs say API keys are the recommended default, and `CODEX_API_KEY` can be set inline for a single run:

```bash
CODEX_API_KEY=<api-key> codex exec --json "triage open bug reports"
```

The rule that matters most: do not set `OPENAI_API_KEY` or `CODEX_API_KEY` as a job-level environment variable in workflows that run repository-controlled code. Anything in that job's environment, such as a test script from a pull request, could read it.

The docs also describe ChatGPT-managed sign-in through `~/.codex/auth.json` as an advanced option. That file holds access tokens, so treat it like a password: don't commit it or paste it into tickets. The docs say not to use this workflow for public or open-source repositories.

For GitHub Actions, the docs say to use the Codex GitHub Action rather than installing the CLI and authenticating it yourself.

## Using the Codex GitHub Action

The action is `openai/codex-action@v1`. A minimal read-only job, adapted from the documented inputs:

```yaml
on:
  workflow_dispatch:
jobs:
  codex:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v5
        with:
          persist-credentials: false
      - id: run_codex
        uses: openai/codex-action@v1
        with:
          openai-api-key: ${{ secrets.OPENAI_API_KEY }}
          prompt: "Summarize the open TODOs in this repository."
          sandbox: read-only
          safety-strategy: drop-sudo
          output-file: codex-output.md
```

The inputs you will use most:

- `prompt` or `prompt-file`: provide exactly one. `prompt-file` points to a file in the repository, which keeps long instructions reviewable.
- `codex-args`: extra `codex exec` flags, as a JSON array or a shell-style string.
- `output-schema` or `output-schema-file`: pass a schema to `--output-schema`. The two are mutually exclusive.
- `model` and `effort`: leave empty for Codex defaults.
- `sandbox`: the legacy sandbox mode (`workspace-write`, `read-only` or `danger-full-access`). Pick the narrowest that lets the task finish.
- `permission-profile`: a built-in or configured profile. It is mutually exclusive with `sandbox`.
- `output-file`: saves the final message to disk.
- `codex-version`: the version of `@openai/codex` to install; blank uses the latest. Pin it for repeatable runs.
- `allow-users`, `allow-bots`, `allow-bot-users`: who may trigger the workflow.

### Safety strategy: what each option does

`safety-strategy` controls how much the action restricts Codex on the runner:

- `drop-sudo` (default): on Linux and macOS, revokes the default user's `sudo` access before Codex runs. On Linux it also blocks runner access to root-owned service sockets under `/run`, including the Docker daemon. These changes can persist on reused self-hosted runners, so use disposable runners and run the action as the last step in the job. On Linux it needs passwordless `sudo`, `setpriv` and `setfacl`.
- `unprivileged-user`: runs Codex as the account named in `codex-user`. That account must be able to read and write the checkout.
- `read-only`: a read-only sandbox that cannot modify files or reach the network directly, but the API key still flows through the proxy, so Codex could read it from process memory. Do not rely on it alone to protect secrets.
- `unsafe`: no privilege reduction; Codex runs as `runner`, which typically has `sudo`. It is the only option on Windows, where GitHub-hosted runners lack a supported sandbox.

### Who can trigger it

By default, only users with write access to the repository can trigger the action. `allow-users` adds named GitHub usernames. `allow-bots: true` lets trusted bots such as `github-actions[bot]` skip the write-access check, and `allow-bot-users` names specific bots. The `*` wildcard is not supported, so list each trusted bot.

## A two-job pull request review

The action's README includes a review workflow worth copying, built from two jobs with different permissions.

1. A `codex` job with only `contents: read` checks out the pull request's merge commit (`refs/pull/<number>/merge`) with `persist-credentials: false`, fetches the base branch and the PR head, and runs the action with `permission-profile: ":workspace"`. The prompt carries the PR number, repository, title and body, and asks Codex to review only the changes the PR introduces. The README notes that the `:workspace` profile does not grant network access.
2. A `post_feedback` job runs after `codex`, only when the output is non-empty. It holds `issues: write` and `pull-requests: write`, and posts the message as a PR comment with `actions/github-script`.

The review job exposes the action's `final-message` output as a job output, and the posting job reads it. The point of the split is that the job that reads untrusted pull request text has no write token, and the job with the write token never runs Codex.

Some rules from the README:

- Permission profiles are in beta and need Codex CLI 0.138.0 or later, so do not combine one with an older pinned `codex-version`.
- Setting both `permission-profile` and `sandbox` fails before Codex starts, and `safety-strategy: read-only` cannot be combined with a profile.
- `codex-home/config.toml` and named profile definitions are trusted workflow configuration, so they must not come from an untrusted checkout.

A review prompt that works well keeps scope tight. Store it in a committed file and pass it with `prompt-file`:

```text
Review only the changes in this pull request.
List correctness bugs, missing tests and security issues, most severe first.
For each, name the file and line. If you find nothing, say so briefly.
Do not suggest style changes.
```

The PR title and body come from outside the repository's trusted code, so treat them as data, not instructions.

## A checklist before you turn it on

- Set `sandbox: read-only` (or a read-only profile) explicitly, and widen only if the task fails for lack of access.
- Keep the API key in GitHub secrets and out of job-level `env`.
- Run on disposable runners, and keep the action as the last step in the job.
- Pin `codex-version` while you tune the prompt.
- Restrict triggers: leave `allow-users` empty unless you need it, and list trusted bots by name.
- Keep the reading job separate from any job that can write to the repository.

## Conclusion

Codex in CI is safer when each layer is narrow: an explicit read-only sandbox, a key passed only to the step that needs it, a reviewer job with no write token, and a short list of who can trigger it. Start with the read-only review, check the comments for a week, and only then give a job write access.

If you want a different approach to checking AI-written code, [Atlas](https://bridge-terminal.com/atlas/) is a team of AI engineers built on Claude Code. It reviews every build as it finishes. It does not use Codex, and it is not a Codex CI setup.

## Questions people ask

### How do I run Codex in CI without a terminal?

Use `codex exec`, which runs once without the interactive screen and prints only the final message to stdout. On GitHub Actions, the docs recommend the openai/codex-action instead of installing and signing in the CLI yourself.

### Can codex exec edit files?

Not by default: it runs in a read-only sandbox. Add `--sandbox workspace-write` to let it edit files, and use `danger-full-access` only in a controlled environment. Note that the GitHub Action uses workspace-write if you set no sandbox or profile.

### Where should I store the API key for Codex in GitHub Actions?

Store it as a GitHub Actions secret and pass it through the action's openai-api-key input. Do not set OPENAI_API_KEY or CODEX_API_KEY as a job-level environment variable in workflows that run repository-controlled code.

### What does the drop-sudo safety strategy do?

It is the action's default. On Linux and macOS it removes the default user's sudo access before Codex runs, and on Linux it also blocks access to root-owned sockets such as Docker's. It can persist on reused self-hosted runners, so use disposable runners and run the action last.

### How do I get structured output from codex exec?

Use `--json` for a JSON Lines event stream, `-o` to write the final message to a file, or `--output-schema` with a JSON Schema file to request a response of a fixed shape.

## About Atlas: Review every build without writing the CI yourself

This article is about wiring Codex into CI. Atlas is different: it is a team of AI engineers built on Claude Code, not Codex. It reviews every build as it finishes, and each comes back releasable or with fix tasks for its builder. — https://bridge-terminal.com/atlas/

## Sources

- [Codex non-interactive mode (codex exec)](https://learn.chatgpt.com/docs/non-interactive-mode)
- [Codex GitHub Action docs](https://learn.chatgpt.com/docs/github-action)
- [openai/codex-action on GitHub](https://github.com/openai/codex-action)

_Researched and drafted with Claude, then checked against the sources above. Atlas is an independent product, not affiliated with Anthropic or OpenAI._

## Related

- [Claude Code GitHub Actions: Set Up @claude on Your Repo](https://bridge-terminal.com/atlas/blog/claude-code-github-actions-setup-at-claude/)
- [AI Code Review with Claude Code: What It Catches and Misses](https://bridge-terminal.com/atlas/blog/ai-code-review-claude-code-setup/)
- [Claude Code vs Codex for Teams: A Workflow Comparison](https://bridge-terminal.com/atlas/blog/claude-code-vs-codex-team-workflows/)
