codex exec vs Interactive Mode
Before wiring Codex into a pipeline, it's important to understand the fundamental difference between the two entry points. Interactive mode (codex) is designed for human use; codex exec is designed for machine invocation.
| Feature | Interactive (codex) |
Non-interactive (codex exec) |
|---|---|---|
| Startup | Opens full-screen terminal UI | Accepts arguments and runs immediately |
| Approval dialogs | May prompt for human confirmation | No prompts — fully automated |
| Exit behavior | Stays open until manually quit | Exits automatically when task is done |
| Exit code | N/A | 0 on success, non-zero on failure |
| Slash commands | Supports /model, /init, etc. |
Not supported — use CLI flags instead |
| Best for | Daily development, codebase exploration | CI/CD pipelines, scripts, cron jobs |
The reliable exit code is the key to CI integration. When a task succeeds, codex exec exits with 0. Combined with set -e or GitHub Actions' default failure detection, any execution failure will immediately halt the pipeline.
Prerequisites
Before using Codex CLI in a CI environment, make sure the following conditions are met:
-
Node.js version: Codex CLI requires Node.js 22 or higher. Most CI images include Node.js, but verify the version meets the requirement.
$ node --version # Must be v22.0.0 or higher -
API Key stored as a CI Secret: This is the most important security requirement. Never put
OPENAI_API_KEYin plain text inside any config file or source code. -
Install @openai/codex: Install it in a CI step via
npm install -g @openai/codex, or add it as a devDependency inpackage.json.
Setting Up the API Key as a Secret
In GitHub, the path to add a repository secret is: Repository → Settings → Secrets and variables → Actions → New repository secret.
| Platform | Where to set it | How to reference it |
|---|---|---|
| GitHub Actions | Settings → Secrets and variables → Actions | ${{ secrets.OPENAI_API_KEY }} |
| GitLab CI | Settings → CI/CD → Variables (check Masked) | $OPENAI_API_KEY |
| CircleCI | Project Settings → Environment Variables | $OPENAI_API_KEY |
If an API Key is ever exposed in logs or committed to a repo, revoke it immediately in the OpenAI dashboard and generate a new one. Consider creating a dedicated API Key for CI/CD use — this makes it easy to monitor usage independently and revoke without disrupting other workflows.
GitHub Actions Basic Configuration
Below is a complete GitHub Actions workflow file showing how to install and run Codex CLI in CI:
name: Codex Automation Task
on:
pull_request:
types: [opened, synchronize]
jobs:
codex-task:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2 # Fetch enough commit history
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install Codex CLI
run: npm install -g @openai/codex
- name: Run Codex Task
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
codex exec --model gpt-4.1-mini \
"Analyze the code changes in this PR and list potential bugs, performance issues, and style problems"
Key notes:
fetch-depth: 2: Ensures the CI runner has enough commit history for Codex to usegit diffto analyze changes.permissions: contents: write: Required if the task needs to write files (e.g. updating CHANGELOG).- The
envblock: The correct place to inject the API Key from Secrets — it will never appear in logs.
3 Practical Task YAML Examples
Task 1: Auto-Generate CHANGELOG
On every merge to main, automatically analyze the commit diff and append a new CHANGELOG entry.
name: Auto Generate CHANGELOG
on:
push:
branches: [main]
jobs:
changelog:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm install -g @openai/codex
- name: Generate CHANGELOG entry
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
codex exec --model gpt-4.1-mini "Read the output of git diff HEAD~1, generate a concise CHANGELOG entry (format: ## vX.X.X - YYYY-MM-DD header followed by a bullet list of changes), and append it to CHANGELOG.md without removing existing content"
- name: Commit CHANGELOG update
run: |
git config --global user.email "[email protected]"
git config --global user.name "Codex CI Bot"
git add CHANGELOG.md
git diff --staged --quiet || git commit -m "chore: auto-update CHANGELOG [skip ci]"
git push
Note the [skip ci] suffix in the commit message — this prevents the CHANGELOG update from triggering another CI run, avoiding an infinite loop.
Task 2: Automated PR Code Review
Every time a PR is opened or updated, Codex analyzes the changes and posts a review comment directly on the PR.
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
ai-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm install -g @openai/codex
- name: Run AI code review
id: review
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
# Capture PR diff to a temp file
git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr_diff.txt
# Let Codex review the diff, write results to a file
codex exec --model gpt-4.1 \
"Read /tmp/pr_diff.txt (a git diff of a pull request) and write a code review covering: 1) potential bugs and edge cases, 2) security vulnerabilities, 3) performance issues, 4) readability suggestions. Save the review to /tmp/review_result.txt"
# Export review content to environment
REVIEW_CONTENT=$(cat /tmp/review_result.txt)
echo "REVIEW_CONTENT<> $GITHUB_ENV
echo "$REVIEW_CONTENT" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Post review comment
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## 🤖 AI Code Review\n\n${process.env.REVIEW_CONTENT}`
})
Task 3: Unit Test Completion
Scan the project for exported functions that lack test coverage and automatically generate test cases. This task works well run after a PR merge or on a schedule.
name: Auto Complete Unit Tests
on:
workflow_dispatch: # Manual trigger, or switch to schedule
schedule:
- cron: '0 2 * * 1' # Every Monday at 2 AM UTC
jobs:
test-completion:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm install -g @openai/codex
- name: Generate missing unit tests
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
codex exec --model gpt-4.1 \
"Scan all .js and .ts files under src/, identify exported functions and classes, then check whether __tests__ or *.test.* files already cover them. For any exported function without a test, generate a Jest unit test. Place each test file in a __tests__ directory next to the source file, named [filename].test.js. Each test must include at least one happy-path case and one edge case."
- name: Create PR with new tests
run: |
git config --global user.email "[email protected]"
git config --global user.name "Codex CI Bot"
git checkout -b feat/auto-tests-$(date +%Y%m%d)
git add '**/__tests__/*.test.*'
git diff --staged --quiet || \
git commit -m "test: auto-generate missing unit tests" && \
git push origin HEAD && \
gh pr create --title "test: auto-generated unit tests" \
--body "Unit tests automatically generated by Codex CI Bot. Please review before merging."
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GitLab CI Adaptation
GitLab CI uses the same core codex exec command. The differences are in config file format and secret injection.
# GitLab CI — Codex automated code review
stages:
- ai-review
variables:
NODE_VERSION: "22"
codex-code-review:
stage: ai-review
image: node:22-slim
only:
- merge_requests
before_script:
- npm install -g @openai/codex
- git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
script:
- git diff origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME...HEAD > /tmp/mr_diff.txt
- codex exec --model gpt-4.1-mini
"Read the git diff in /tmp/mr_diff.txt, identify potential bugs, security issues, and performance bottlenecks, then write a code review to /tmp/review.txt"
- cat /tmp/review.txt
variables:
OPENAI_API_KEY: $OPENAI_API_KEY # Read from CI/CD Variables
artifacts:
paths:
- /tmp/review.txt
expire_in: 1 week
codex-changelog:
stage: ai-review
image: node:22-slim
only:
- main
before_script:
- npm install -g @openai/codex
- git config --global user.email "[email protected]"
- git config --global user.name "Codex CI"
script:
- codex exec --model gpt-4.1-mini
"Generate a CHANGELOG entry from git diff HEAD~1 and append it to CHANGELOG.md"
- git add CHANGELOG.md
- git diff --staged --quiet || git commit -m "chore: update CHANGELOG [skip ci]"
- git push https://ci-token:$CI_JOB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git HEAD:main
variables:
OPENAI_API_KEY: $OPENAI_API_KEY
| Difference | GitHub Actions | GitLab CI |
|---|---|---|
| Config file | .github/workflows/*.yml |
.gitlab-ci.yml (repo root) |
| Secret reference | ${{ secrets.KEY }} |
$KEY (set in CI/CD Variables) |
| PR/MR trigger | on: pull_request |
only: - merge_requests |
| Built-in token | secrets.GITHUB_TOKEN |
$CI_JOB_TOKEN |
| Repo info | github.repository |
$CI_PROJECT_PATH |
In GitLab CI's Variables settings, always check Masked to prevent OPENAI_API_KEY from appearing in job logs. Checking Protected additionally restricts the variable to protected branches (e.g. main) only.
Security Best Practices
Integrating AI tools into CI/CD requires extra attention to security. Here are the most important points:
API Key Management
- Use Secrets storage: Never put the API Key in code, config files, or YAML.
- Dedicated CI Key: Create a separate API Key for CI/CD so you can monitor its usage independently and revoke it without affecting other workflows.
- Least privilege: Check the OpenAI dashboard for usage limits — set a monthly cap to prevent unexpected overspend.
- Rotate regularly: Rotate CI/CD API Keys every 90 days as a standard practice.
Sandbox Mode in CI
In a CI environment, Codex defaults to workspace-write sandbox mode, meaning it can only read and write files within the working directory — it cannot touch system files. For most CI tasks, this default is already safe.
# Read-only mode: Codex can only read files, not modify anything
# Ideal for pure review tasks — guarantees CI will never change code
codex exec --sandbox-mode read-only \
"Analyze all files in src/ and identify potential security vulnerabilities"
Avoiding Accidental Changes
- Define clear boundaries: Explicitly state which files Codex may modify in your prompt, e.g. "only update CHANGELOG.md, do not touch any other files".
- Log staged changes: Before committing, add a
git diff --stagedstep so the CI log records what actually changed. - PR over direct push: For tasks like test generation, have Codex open a PR rather than pushing directly to the main branch so a human can review before merging.
When running codex exec in CI, the more specific your prompt is, the better. Vague instructions can cause Codex to modify files beyond the intended scope. Always verify actual changes with git diff or git status before committing.
Cost Control
API calls accumulate costs in CI/CD. A few straightforward choices can significantly reduce your bill:
Choosing the Right Model
| Model | Relative cost | Recommended for |
|---|---|---|
gpt-4.1 |
High | Complex code review, tasks requiring deep comprehension |
gpt-4.1-mini |
Low (~1/20 of gpt-4.1) | Changelog generation, simple formatting, routine PR checks |
gpt-4.1-nano |
Very low | Simple text processing, quick classification tasks |
# For simple CHANGELOG generation, mini is more than enough
codex exec --model gpt-4.1-mini \
"Generate a concise CHANGELOG entry from git diff HEAD~1"
# For in-depth security analysis, use the full model
codex exec --model gpt-4.1 \
"Thoroughly analyze this PR for security vulnerabilities including SQL injection and XSS"
Optimizing Trigger Conditions
- Branch filters: Use
branchesto only run on important branches likemainordevelop. - Path filters: Use
pathsto trigger only when relevant directories change. - Manual triggers: For expensive tasks like full test generation, use
workflow_dispatchfor on-demand runs instead of automatic triggers.
on:
push:
branches: [main, develop]
paths:
- 'src/**' # Only trigger when src/ changes
- '!src/**/*.md' # Exclude documentation files
Common Errors & Troubleshooting
401 Unauthorized — Invalid or missing API Key
The most common error. Troubleshooting steps:
- Check the Secret name spelling (case-sensitive) — it must be
OPENAI_API_KEY. - Confirm the correct reference in the
envblock:OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}. - Check whether the Secret is set at the right level (repository vs. organization).
- Verify in the OpenAI dashboard that the key is still active, not expired, and has sufficient credits.
Execution timeout — CI job hangs for a long time
Codex may take longer on complex tasks. Solutions:
- Set
timeout-minutes: 10on the step in GitHub Actions to cap execution time. - Simplify the task prompt, or break a large task into multiple smaller sequential steps.
- Use
--model gpt-4.1-minifor faster response times.
Environment variable not set — Codex can't find the API Key
Codex reads the key from the OPENAI_API_KEY environment variable automatically. If it reports the key is missing:
- Verify the
envblock is at the same level asrun, not incorrectly nested underjobsorsteps. - Add a debug step:
run: echo "Key is set: ${{ secrets.OPENAI_API_KEY != '' }}"— this confirms the Secret is non-empty without revealing its value.
git diff returns empty — can't analyze changes
The default checkout action only fetches the latest commit (fetch-depth: 1), causing git diff HEAD~1 to fail. Fix:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # Fetch last 2 commits to support HEAD~1
# Or fetch-depth: 0 for full history
Node.js version too old — installation fails
Codex CLI requires Node.js 22+. Use actions/setup-node@v4 to explicitly pin the version:
- uses: actions/setup-node@v4
with:
node-version: '22' # Explicit version, don't rely on runner defaults