Claude Code in GitHub Actions
How do you run a team of Claude Code agents in one workflow?
Several Claude Code agents in one GitHub Actions workflow: the strategy.matrix that fans one job out into many, a brief and a budget for each, needs to make a reviewer wait for them, and how that differs from subagents.

Listen to this guide
The short answer
Write one job, fan it out with strategy.matrix, and put the job that reads their work behind needs. GitHub starts every matrix combination at the same time, each on a runner of its own, so three agents with three briefs is one job block and a list of three.
That is parallelism in the workflow file, not inside the model. Claude Code's own subagents run within a single session on one machine, sharing its transcript and its budget. Matrix jobs are separate runs: separate checkouts, separate caps, separate logs, and one that fails does not take the others down.
One workflow, several agents
- A job is the unit. Jobs in a workflow start together unless something says otherwise, and each gets a fresh virtual machine. Two agents means two jobs, and needs and matrix is the reference for both keys.
- A matrix writes those jobs for you. One job block plus an axis of values becomes one job per value, so adding a fourth agent is a fourth list item rather than a fourth copy of the YAML.
- Nothing is shared between them. Separate runners mean separate checkouts and separate working trees. Nothing one agent writes is visible to another unless it lands in the repository or in a job output.
- So give each one its own branch. Two agents pushing the same branch race, and the loser's commit is rejected or overwritten. A branch per agent keeps every result reviewable.
- Split by area, not by step. A team works when each brief owns a different part of the repository. Two agents editing the same files produce two pull requests that conflict with each other.
- Subagents are a different mechanism. They live in
.claude/agents/, run inside one Claude Code session, and answer to that run's--max-turnsand its timeout. Useful, but one job, one log, one failure.
The matrix that fans them out
- The axis holds objects, not just strings. Each entry carries whatever the job needs to read, so
matrix.agent.briefandmatrix.agent.idcome out of the same list the fan-out is built from. fail-fast: falseis the setting that matters here. Left at GitHub's default oftrue, the first agent to fail cancels every other agent mid-run, and you pay for the tokens they spent before the cancel.max-parallelthrottles the fan-out. An API key has a rate limit, and ten agents hitting it at once is how a run fails for reasons that have nothing to do with the code.- A matrix caps at 256 jobs per workflow run, which is far beyond anything worth paying for. The real limit is the concurrent job allowance on your GitHub plan.
- Save this as
.github/workflows/agent-team.ymlon the default branch. Labelling an issueteamstarts three agents, and the fourth job waits for all of them.
name: Agent team
on:
issues:
types: [labeled]
# One team per issue; a second label on the same issue queues behind the first.
concurrency:
group: agent-team-${{ github.event.issue.number }}
cancel-in-progress: false
jobs:
agents:
if: github.event.label.name == 'team'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
strategy:
# One agent failing leaves the rest to finish; the default would cancel them mid-run.
fail-fast: false
max-parallel: 2
matrix:
agent:
- id: api
area: src/api
brief: Change the HTTP handlers and their tests. Leave the client and the docs alone.
- id: web
area: src/web
brief: Change the client and its tests. Leave the handlers and the docs alone.
- id: docs
area: docs
brief: Change the documentation to match the issue. Change no source file.
steps:
- uses: actions/checkout@v7
- name: Work on ${{ matrix.agent.area }}
uses: anthropics/claude-code-action@v1
with:
# A Claude subscription signs in with claude_code_oauth_token and secrets.CLAUDE_CODE_OAUTH_TOKEN instead of this line.
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
prompt: |
Issue #${{ github.event.issue.number }} in ${{ github.repository }} is the work, and this checkout is the default branch.
Read it with `gh issue view ${{ github.event.issue.number }}`, then do only your own part of it.
Your part: ${{ matrix.agent.brief }}
Touch nothing outside ${{ matrix.agent.area }}; another agent owns the rest and is working right now.
Finished: commit on branch agent/${{ github.event.issue.number }}-${{ matrix.agent.id }}, push it, and open a pull request whose body says which part you did.
Nothing worth changing in your area: say so and stop, without an empty pull request.
claude_args: |
--allowedTools "Bash,Edit,Write,Read,Glob,Grep"
--max-turns 30
--max-budget-usd 4
review:
# Every agent above has to finish before this reads what they landed.
needs: agents
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
pull-requests: read
issues: write
steps:
- uses: actions/checkout@v7
- name: Comment the team's result on the issue
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
display_report: true
prompt: |
Three agents worked on issue #${{ github.event.issue.number }} in ${{ github.repository }} and each opened its own pull request.
List them with `gh pr list --search "agent/${{ github.event.issue.number }}-"` and read each diff with `gh pr diff`.
Comment once on the issue: what each part changed, where two of them disagree, and what is still missing.
Judge only what is in the diffs; open no pull request and change no file.
claude_args: |
--allowedTools "Bash,Read,Glob,Grep"
--max-turns 20
--max-budget-usd 2
Giving each one its own brief and caps
- The brief comes from the matrix, not from three prompts. One
promptblock readsmatrix.agent.brief, so the shared instructions stay written once and only the part that differs is in the list. - Say what the agent must not touch. A brief that names an area but not its boundary produces three agents that all fix the same typo, and three pull requests that conflict.
--max-budget-usdcaps the money and--max-turnscaps the loop. They are per job, so a matrix of three multiplies both, and the number to plan against is the total.timeout-minutescaps the runner. Without it a hung agent holds a machine for GitHub's default six hours; timeouts and concurrency covers that key and theconcurrencyblock above it.--allowedToolsis per agent too. The reviewer above never getsEditorWrite, so the job that judges the work cannot quietly rewrite it.- Permissions belong on the job, not the workflow. The agents need
contents: writeto push; the reviewer needsissues: writeand nothing else. Permissions and secrets sets out the scopes.
Making one wait for another
needsis the whole mechanism. A job naming another inneedsstarts only after that one finishes, and a job naming a matrix job waits for every combination of it.- By default it waits for success. If any agent fails, the reviewer is skipped.
if: ${{ !cancelled() }}on the waiting job runs it anyway, which is what you want when two agents out of three landing is still worth reading. - Outputs are the only thing that crosses. A job passes a value on through
outputs, read asneeds.<job>.outputs.<name>. Files do not cross; a branch or an artifact does. - A planner in front is the other shape. One agent reads the issue, writes the split as a job output, and the matrix reads that output, so the team's size is decided per issue rather than in the file.
concurrencystops two teams colliding. Grouped by issue number, a second label on the same issue queues instead of putting six agents on the same branches.
What goes wrong with a team
- The bill multiplies, not the clock. Three agents cost three agents' tokens whether they run at once or in turn. Parallelism buys wall-clock time and nothing else.
- Overlapping briefs are the usual failure. Two agents given a whole repository will both find the same obvious thing, and neither pull request merges cleanly.
- The default
fail-fastthrows work away. One agent hitting a rate limit cancels two agents that were nearly finished, and you are billed for what they spent. - A shared branch loses commits. Two jobs pushing the same ref means one push is rejected, and an agent told to retry may force it instead.
- The logs are per job, not per team. Nothing joins three transcripts into one story, so the reviewer job exists to write that summary while the run is still fresh.
- Reruns are per job. A failed matrix combination reruns on its own, which is cheap, but it reruns against a repository the other agents have since changed.
- A team is worth it when the parts are genuinely separate. For one change to one area, one agent with a longer timeout beats three agents negotiating through pull requests. Claude Code in GitHub Actions is that single-agent file.
What Repo Fleet does about it
Repo Fleet is where you build workflows like this one and watch them run, across every repository you connect. The file stays what this guide built: GitHub Actions in your repository, on your runners, billed to your own Anthropic key. Repo Fleet holds no key and meters nothing.
- The matrix is a section of the job card: the axes as rows, with
fail-fastandmax-parallelbeside them and a count of the jobs they come to. - Each job's Waits for chips are
needs, so the shape of the fan-out and the fan-in is visible without reading the YAML. - Every edit is checked as you type by the same checks that pass the workflow on this page, including a job missing its timeout and a grant wider than the run needs.
- The runs page lists each repository's runs newest first, so a team that started failing shows up without opening the Actions tab.
Install it on GitHub, pick the repositories it may see, and open the workflow in the editor. The matrix is the list; the team is what it fans out to.