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/--forceso 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
- https://code.claude.com/docs/en/headless
- https://code.claude.com/docs/en/permissions
- https://code.claude.com/docs/en/costs
- https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md
- https://developers.openai.com/codex/cli/reference
- https://developers.openai.com/codex/permissions
- https://cursor.com/docs/cli/headless
- https://cursor.com/docs/agent/hooks