# The Codex GitHub Action: openai/codex-action setup and safety

> The Codex GitHub Action is openai/codex-action@v1. Its inputs, auth, safety strategy, permission profiles and a pull request review you can paste.

Source: https://latchkey.dev/learn/agent-ci/codex-github-action  
Updated: 2026-11-12

OpenAI's Codex GitHub Action is `openai/codex-action@v1`: it installs the Codex CLI, starts a Responses API proxy that holds your `OPENAI_API_KEY`, drops `sudo`, and runs `codex exec` with your prompt. It never comments, commits or pushes on its own, so the safe shape is two jobs: Codex with read-only repository access, then a separate job that posts its `final-message`.

Codex reaches GitHub in two ways that are easy to confuse. Codex cloud has a code review tool of its own; `openai/codex-action` is a building block you put in a workflow you own, and its README presents it as the way to build your own review bot when you want more control.

The version covered is v1.13, the latest tag as of 2026-11-12: what it does on the runner, the two separate controls over what Codex can touch, the permissions each job needs, and a review workflow pinned to commits.

## What the action does on the runner

The composite steps run in a fixed order. It checks that the triggering actor has write, maintain or admin access, using the job's own token; installs `@openai/codex` (the version in `codex-version`, or the latest); starts the Responses API proxy, which only starts when `openai-api-key` is set; applies the safety strategy; and runs `codex exec` against `prompt` or `prompt-file`, exactly one of which you supply.

What comes back is text: the `final-message` output and, if you set `output-file`, the same message on disk. Anything that touches GitHub afterwards is a step you write. On Windows the only accepted `safety-strategy` is `unsafe`, and the action exits early otherwise, because GitHub-hosted Windows runners have no supported sandbox.

| Input | Default | What it controls |
| --- | --- | --- |
| `openai-api-key` | empty | Key handed to the proxy; required for the proxy to start |
| `responses-api-endpoint` | empty | Full Responses URL for Azure, including `v1/responses` |
| `prompt` / `prompt-file` | empty | The task; provide exactly one |
| `permission-profile` | empty | `:read-only`, `:workspace`, or a named profile |
| `sandbox` | empty | Legacy; `workspace-write` when neither is set |
| `safety-strategy` | `drop-sudo` | Privileges of the Codex process itself |
| `allow-users` / `allow-bot-users` | empty | Extra accounts allowed past the write-access check |
| `model` / `effort` | empty | Codex defaults when empty |

## Two controls, and they are not the same thing

`safety-strategy` decides what the Codex process can do on the host. The default, `drop-sudo`, removes the runner user's `sudo` before Codex starts, and on Linux also removes access to root-owned sockets under `/run` such as Docker's, and starts Codex with `no_new_privs`. It is irreversible for the job, which is why OpenAI recommends running the action as the last step. `unprivileged-user` runs Codex as an account you created, `read-only` forces a read-only sandbox but leaves Codex running as a user that likely has `sudo`, and `unsafe` restricts nothing.

`permission-profile` decides what the commands Codex runs can read, write and reach on the network. `:workspace` lets it edit the checkout and grants no network access, so install dependencies in a step before Codex runs. Profiles are beta, need Codex CLI 0.138.0 or later, and cannot be combined with `sandbox` or with `safety-strategy: read-only`; supplying both fails before Codex starts.

One trap gets its own warning in OpenAI's guide: read-only file access plus no network is not enough on a GitHub-hosted runner, because passwordless `sudo` is the default there and procfs exposes process memory to a privileged reader. Keep `drop-sudo` or `unprivileged-user` whenever the key is on the machine.

```.github/workflows/codex-review.yml, the Codex step
- name: Run Codex
        id: run_codex
        uses: openai/codex-action@bdf19a4a223ec2549a3e2274a0cf61556bc07675 # v1.13
        with:
          openai-api-key: ${{ secrets.OPENAI_API_KEY }}
          permission-profile: ":workspace"
          safety-strategy: drop-sudo
          prompt-file: .github/codex/prompts/review.md
```

> Recent releases changed the requirements, per CHANGELOG.md at v1.13: v1.10 (2026-07-02) added permission profiles, v1.12 (2026-08-20) required unprivileged user namespaces for Linux `drop-sudo`, and v1.13 (2026-10-05) requires `setfacl` from the `acl` package. Self-hosted Linux runners need all three.

## A review on every pull request

This is OpenAI's own pattern, pinned. The first job checks out the merge commit with `persist-credentials: false`, so the job token is not left in `.git/config` for Codex to read, fetches the base and head refs, and runs Codex with only `contents: read`. Its message leaves through a job output. The second job, which holds `pull-requests: write` and `issues: write` but never the OpenAI key, posts it.

Branch and ref names go through `env:` and are quoted in the shell, as the action's security guide asks, because a `${{ }}` expression is expanded into the script before the shell parses it.

```.github/workflows/codex-review.yml
name: Codex review
on:
  pull_request:
    types: [opened, synchronize, reopened]
jobs:
  codex:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      final_message: ${{ steps.run_codex.outputs.final-message }}
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          ref: refs/pull/${{ github.event.pull_request.number }}/merge
          persist-credentials: false
      - env:
          PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
        run: git fetch --no-tags origin "$PR_BASE_REF" "+refs/pull/$PR_NUMBER/head"
      - id: run_codex
        uses: openai/codex-action@bdf19a4a223ec2549a3e2274a0cf61556bc07675 # v1.13
        with:
          openai-api-key: ${{ secrets.OPENAI_API_KEY }}
          permission-profile: ":read-only"
          prompt-file: .github/codex/prompts/review.md
  post_feedback:
    runs-on: ubuntu-latest
    needs: codex
    if: needs.codex.outputs.final_message != ''
    permissions:
      issues: write
      pull-requests: write
    steps:
      - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
        env:
          CODEX_FINAL_MESSAGE: ${{ needs.codex.outputs.final_message }}
        with:
          script: |
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.pull_request.number,
              body: process.env.CODEX_FINAL_MESSAGE,
            });
```

## Who can trigger it, and what refusal looks like

By default only users with write access can run the action. `allow-users` adds named accounts or `*`, `allow-bots` lets `github-actions[bot]` through, and `allow-bot-users` lists other trusted bots explicitly, since `*` is not accepted there. OpenAI's security guide warns what `allow-users: "*"` invites on a public repository: strangers spending your API quota through issues they open.

A refused actor fails the step with a message built from the source below, so the detected permission tells you whether the account is a reader or not a collaborator at all.

```openai/codex-action v1.13 src/checkActorPermissions.ts, template strings as written
Actor '${actor}' must have write access to ${owner}/${repo}. Detected permission: '${permission}'.
Actor '${actor}' is not a collaborator on ${owner}/${repo}; write access is required.
```

## Fork pull requests and untrusted text

On a `pull_request` from a fork GitHub withholds secrets, so `OPENAI_API_KEY` is empty and the proxy steps are skipped, since each is conditioned on `openai-api-key` being set. Codex has no key to reach the model with. That is the boundary working. Moving to `pull_request_target` to get the key back puts the fork's text next to your secrets, which is the pwn request that [pull_request_target security](/learn/ci-security/pull-request-target-security) takes apart, and the trust split for agent-authored changes is on [testing AI-generated pull requests in CI](/learn/agent-ci/test-ai-generated-pull-requests-in-ci).

Same-repository pull requests carry their own risk. The PR title, body, commit messages, screenshots and any `AGENTS.md` in the checked-out code are all input the model reads, and OpenAI's guide lists each of them as a prompt injection surface. A read-only profile in the job that holds the key and a write-capable job that holds no key is what keeps a successful injection from becoming a push; [prompt injection in CI agents](/learn/ci-security/prompt-injection-in-ci-agents) works through that split with Clinejection as the case.

## Read from the action, not from a run

There is no recorded run behind this page and no entry for it in `content/repro-evidence.mjs` or `content/heal-evidence.mjs`. The inputs, defaults and refusal messages were read from `openai/codex-action` at v1.13 (`action.yml`, `README.md`, `docs/security.md`, `CHANGELOG.md` and `src/`) and from OpenAI's Codex pages on 2026-11-12. A captured review would show one model's opinion of one diff, which says nothing about how the action is wired.

## FAQ

### Does openai/codex-action work on Windows runners?

Only with `safety-strategy: unsafe`. GitHub-hosted Windows runners have no sandbox the action supports, so it validates the input and exits early with any other value. That leaves Codex with the runner user's full privileges, so keep Windows runs to trusted prompts.

### How do I use codex-action with Azure OpenAI?

Set `responses-api-endpoint` to the full Responses URL, including the `v1/responses` suffix and any required query parameters, and keep `openai-api-key` set to a key that works as a Bearer token against that endpoint. The proxy still sends `Authorization: Bearer <key>` upstream.

### Why do I get permission errors after drop-sudo?

Because Codex now runs without `sudo` and cannot fix file ownership itself. Grant access before the action runs, for example `chmod -R g+rwX "$GITHUB_WORKSPACE"`, or use the `unprivileged-user` pattern. Steps that need `sudo` or Docker belong before the action or in a fresh job.

### Can openai/codex-action push commits or open pull requests?

Not by itself. It returns the final message and leaves the working tree as Codex left it. A later step with a write token can commit, push or open a pull request, and that step is where to apply the same review you would apply to any automation that writes to the repository.

## References

- [openai/codex-action v1.13 action.yml (read 2026-11-12)](https://github.com/openai/codex-action/blob/v1.13/action.yml)
- [openai/codex-action security guide](https://github.com/openai/codex-action/blob/v1.13/docs/security.md)
- [OpenAI: Codex GitHub Action](https://developers.openai.com/codex/github-action)
- [OpenAI: Codex permissions](https://developers.openai.com/codex/permissions)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
