A red build at 2 a.m. used to mean waking a human to read the logs and push a fix. In 2026 you can hand that first pass to a coding agent. This tutorial sets up a coding agent in CI that auto-fixes failing tests on its own branch — the trigger, scoped permissions, the loop-until-green prompt, cost caps, and a human approval gate before anything merges. Every block below is real YAML you can paste, not a diagram of a system you still have to build.
The pattern is now a shipping product category. GitHub released one-click "Fix with Copilot" for failing Actions (GA, May 18 2026) and Agentic Workflows (public preview, June 11 2026). What follows is the transparent, portable version of the same idea: hand-rolled workflow YAML you fully control, runnable on any runner, with no managed black box in the middle.
Step 1: Trigger only on a red build
The common mistake is wiring the agent to push and paying for a run on every commit. Instead, fire a second workflow when your existing CI workflow finishes, and guard it on failure. This is the OpenAI Codex cookbook pattern — the agent wakes up only when the build is already red.
# .github/workflows/auto-fix.yml
name: auto-fix-tests
on:
workflow_run:
workflows: ["CI"] # must match the name: of your test workflow
types: [completed]
jobs:
autofix:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
timeout-minutes: 15
concurrency:
group: autofix-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: true
Three things already earn their place here. The if: guard means you never spend a token on a green run. timeout-minutes: 15 is a hard ceiling on the job. The concurrency block with cancel-in-progress kills a stale fix run the moment a newer push supersedes it.
Step 2: Scope permissions to the minimum
The agent needs exactly two write scopes: commit code, and open a PR. Nothing else.
permissions:
contents: write
pull-requests: write
That's the whole list. Both the Codex cookbook and anthropics/claude-code-action use precisely this pair. Add id-token: write only if you authenticate to a cloud provider over OIDC (Bedrock or Vertex). GitHub's own Agentic Workflows push least-privilege further — their agents run read-only by default, and every write goes through a vetted "safe outputs" path. You can't fully reproduce that in raw YAML, but the principle carries: grant write where the agent must write, and nowhere else.
Step 3: Use an app token so CI re-runs on the fix
This is the trap that eats an afternoon. Commits made with the default GITHUB_TOKEN do not trigger downstream workflows — GitHub blocks that to prevent infinite recursion. So your auto-fix PR opens, sits there, and never gets a green check, because CI never ran against it.
The fix is to commit with a GitHub App token instead.
steps:
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ vars.AUTOFIX_APP_ID }}
private-key: ${{ secrets.AUTOFIX_APP_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
ref: ${{ github.event.workflow_run.head_branch }}
Now the agent's commits are attributed to the app, and CI actually runs to validate the fix.
Step 4: Dispatch the agent with a loop-until-green prompt
The loop lives in the prompt, not the YAML. You tell the agent to run the suite, fix, and re-run until it's green — and you cap how long it can spin with --max-turns.
Here's the Claude path, now GA at anthropics/claude-code-action@v1. The @beta era is over and the move carries breaking changes: direct_prompt became prompt, and max_turns/model collapsed into a single claude_args string.
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: "--max-turns 5 --model claude-sonnet-4-6"
prompt: |
The test suite on this branch is failing. Read the
repository, run the test suite, identify the minimal
change needed to make all tests pass, implement only
that change, and stop. Do not refactor unrelated code.
Do not edit the tests to make them pass unless a test
is provably wrong.
--max-turns defaults to 10; setting it to 5 is your outer kill-switch so the agent can't loop forever. Use prompt: — not an @claude mention — for autonomous, non-interactive runs.
Prefer Codex? The official action mirrors the structure, and sandbox_mode=workspace-write is the concrete guardrail: the agent can edit the working tree but stays contained there.
- uses: openai/codex-action@main
with:
openai_api_key: ${{ secrets.OPENAI_API_KEY }}
codex_args: '["--config","sandbox_mode=\"workspace-write\""]'
prompt: |
Read the repository, run the test suite, identify the
minimal change needed to make all tests pass, implement
only that change, and stop.
That prompt — "run the suite, identify the minimal change, implement only that change, and stop" — is worth copying verbatim. "Run → fix → re-run" is the agent's internal loop; the minimal-change clause is what stops it from rewriting half the codebase to silence one assertion.
Step 5: Commit to the agent's own branch and open a PR
The agent must never push to the base branch. Pair it with peter-evans/create-pull-request@v6, which writes to a named branch and opens a PR — both reference workflows use exactly this action.
- uses: peter-evans/create-pull-request@v6
with:
token: ${{ steps.app-token.outputs.token }}
branch: codex/auto-fix-${{ github.event.workflow_run.run_id }}
base: ${{ github.event.workflow_run.head_branch }}
title: "fix(ci): auto-fix failing tests"
commit-message: "fix(ci): auto-fix failing tests via agent"
body: |
Automated fix for the failing build on
`${{ github.event.workflow_run.head_branch }}`.
Review the diff before merging.
Routing every fix through a PR is what makes the human gate structural instead of a matter of discipline. The agent proposes; a person disposes.
Step 6: Keep the human approval gate
Do not let agent commits auto-merge. The PR review is the approval gate. GitHub's "Fix with Copilot" is explicit about this shape — the agent "investigates the failure, pushes a fix to your branch, and tags you for review." Anthropic's docs say it plainly: review the suggestions before merging.
Then make skipping it impossible. Turn on branch protection for your base branches, and require at least one approving review plus passing status checks:
gh api -X PUT repos/:owner/:repo/branches/main/protection \
-F required_pull_request_reviews.required_approving_review_count=1 \
-F required_status_checks.strict=true \
-F enforce_admins=true
Now the agent's PR can't merge until a human approves it and CI — running properly thanks to the app token from Step 3 — goes green.
Step 7: Stack your cost caps
Cost control isn't one knob; it's layers. Stack all of these and a runaway agent is bounded on every axis:
- The
if:failure guard — you never pay on a green build (Step 1). --max-turns— bounds agent iterations; 5 is plenty for a test fix (Step 4).timeout-minutes— a wall-clock ceiling on the whole job (Step 1).concurrencywithcancel-in-progress— one fix run per branch, no duplicates piling up (Step 1).- Model choice — reach for Sonnet (
claude-sonnet-4-6) to fix tests, not Opus. Spending on the most expensive model for a mechanical task is pure waste.
Anthropic's optimization docs name exactly these levers. Each is cheap to add, and the combination is what keeps a misbehaving run from quietly burning your budget overnight.
What you end up with
A single auto-fix.yml that sleeps through every green build, wakes on red, spins up an agent on a scoped token, lets it loop to green inside a hard turn-and-time budget, and opens a PR on its own branch for a human to approve. No black box, no vendor lock-in — just a workflow file you can read top to bottom and tune to your repo.
Start with --max-turns 5 and the failure guard. Watch the first few PRs it opens, tighten the prompt wherever it overreaches, and only then loosen the budget. The agent handles the boring first pass; you keep the merge button.
