Unattended agents in CI

Verified against Claude Code 2.1.263, Codex CLI 0.153.4

I want the agent to edit. I do not want it to commit, push, or talk to the network on its own. CI is where that split pays off. The job is three steps: run the agent, run the proof, open the PR with a fixed script.

Read Permissions and unattended runs first. This page is the GitHub Actions wiring.

Shared job shape

permissions:
  contents: write
  pull-requests: write

jobs:
  agent:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm ci
      - name: Agent edit
        run: # tool-specific command below
      - name: Prove it
        run: npm test && npm run build
      - name: Open PR
        if: success()
        run: |
          git config user.name "github-actions"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git checkout -B agent/fix
          git add -A
          git diff --cached --quiet && echo "no changes" && exit 0
          git commit -m "fix: agent change"
          git push -u origin agent/fix
          gh pr create --fill --head agent/fix || true
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

The agent step never runs git commit or gh. If those commands are in the allowlist, I made a mistake.

In Claude Code

- name: Agent edit
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
  run: |
    claude --bare -p "Fix the failing tests. Do not commit." \
      --permission-mode dontAsk \
      --permission-prompts none \
      --allowedTools "Read,Edit,Bash(npm test *),Bash(npm run build *)" \
      --max-budget-usd 8

--bare keeps the runner’s ~/.claude out of the job. --permission-mode dontAsk plus --permission-prompts none (v2.1.259+) means a missing allow rule is a denial, not a hang. --max-budget-usd is the session cost cap Claude Code compares to the /usage figure.

In Codex

- name: Agent edit
  run: |
    codex exec --sandbox workspace-write --ask-for-approval never --ignore-user-config \
      "Fix the failing tests. Do not commit or push."

codex exec is the non-interactive command. Default sandbox is configuration. I set workspace-write so it can edit the checkout and never so it does not wait for a prompt. --full-auto is deprecated. --ignore-user-config skips the runner user’s config.toml. Auth still reads CODEX_HOME.

Do not pass --dangerously-bypass-approvals-and-sandbox here.

In Cursor

- name: Agent edit
  env:
    CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
  run: |
    curl https://cursor.com/install -fsS | bash
    agent -p --force "Fix the failing tests. Do not commit or push."

-p is print mode. --force applies edits. Without it the agent only proposes. Cloud agents already run .cursor/hooks.json from the repo. This CLI path does not, so the “Prove it” step is the gate.

Denylist and cost

  • Deny Bash(git push *), Bash(gh *), Bash(curl *) unless the job has no secrets. Prefer leaving them out of the allowlist.
  • Cap spend. Claude Code: --max-budget-usd. Cursor and Codex: I did not find an equivalent flag on the pages I opened, so the job timeout is the cap.
  • Fail the workflow if “Prove it” is red. A red build with a green agent step is a failed job, not a PR.

Checklist

  • Agent step cannot commit or open a PR
  • Allowlist names the exact commands
  • dontAsk / never / --force so the job cannot hang on a prompt
  • Tests and build run after the agent
  • PR step is a script, not a prompt
  • Secrets are job-scoped and not passed into package install scripts

Sources