Atlas
TechnicalHow-to

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.

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.
On this page
  1. The short version
  2. What does codex exec do?
  3. Sandbox and approvals in non-interactive mode
  4. How do I get machine-readable output?
  5. How should I handle the API key in CI?
  6. Using the Codex GitHub Action
  7. A two-job pull request review
  8. A checklist before you turn it on
  9. Conclusion
  10. Questions people ask

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 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.

Sources

  1. Codex non-interactive mode (codex exec)
  2. Codex GitHub Action docs
  3. openai/codex-action on GitHub

Researched and drafted with Claude, then checked against the sources above. Atlas is an independent product, not affiliated with Anthropic or OpenAI. Claude and Claude Code are trademarks of Anthropic; Codex is a product of OpenAI.