The hidden CI tax of AI coding agents
AI coding agents open more PRs and push more often, and every push runs CI. How to model the extra cost, measure the agent share, and cut it.
Eddie Wang
Updated 10 min read
Coding agents like Copilot, Claude Code and Codex change how often code reaches CI. An agent opens a draft PR, pushes a fix, pushes another after review, and sometimes abandons the branch for a new approach. Every one of those pushes runs your full pipeline.
GitHub's Octoverse 2025 report shows the volume moving: 518.7 million pull requests merged (+29% year over year), 11.5 billion Actions minutes in public projects (+35%), and nearly 80% of new developers using Copilot in their first week. Minutes in public repos on standard runners are free; private repos pay for them once the plan's included minutes run out.
Below is a model of what agent-driven PR volume does to a GitHub Actions bill, a way to measure the agent share in your own repos, and the workflow changes that matter most when agents push often. For general cost tactics that apply with or without agents, see GitHub Actions cost optimization.
How agent volume changes the math
Start with a single 15-minute Linux job on GitHub's standard 2-core runner at $0.006/min. The PR and push counts below are assumptions for illustration, not measurements. Replace them with your own numbers from the measurement section.
Before agents (assumed: 5 PRs/week, 2 pushes/PR):
5 × 2 × 15 min × $0.006 = $0.90/dev/week → $3.60/dev/month
With agents (assumed: 15 PRs/week, 3 pushes/PR):
15 × 3 × 15 min × $0.006 = $4.05/dev/week → $16.20/dev/month
Multiplier: 4.5x
For a 30-person team that is $486 a month instead of $108. The multiplier comes from two factors that compound: three times the PRs and 1.5 times the pushes per PR.
A worked example with a matrix and a macOS job
Most pipelines are bigger than one Linux job. Take a 20-engineer team with a TypeScript monorepo, a Node 20 and Node 22 matrix, and one macOS job for native module tests, using the same PR and push assumptions as above.
Pipeline per push:
2× Linux (Node 20 + Node 22): 2 × 15 min × $0.006 = $0.18
1× macOS (native tests): 10 min × $0.062 = $0.62
Total per push: $0.80
Before agents: 20 devs × 5 PRs × 2 pushes × 4 weeks = 800 runs
800 × $0.80 = $640/month
With agents: 20 devs × 15 PRs × 3 pushes × 4 weeks = 3,600 runs
3,600 × $0.80 = $2,880/month
Delta: +$2,240/month (+350%)
The macOS job accounts for $2,232 of the $2,880. At more than 10 times the Linux rate, a single macOS job decides most of the bill, and that share grows with push volume.
The model ignores included minutes. Each push uses 40 job-minutes, so 3,600 pushes use 144,000 job-minutes a month. GitHub Team includes 3,000 minutes and Enterprise Cloud 50,000 (GitHub Actions billing), so most of this volume is billed either way. GitHub also rounds each job up to the next whole minute, so many short jobs cost more than their runtime suggests.
The cost formula
Monthly CI cost = developers × PRs_per_week × pushes_per_PR × 4
× Σ(job_minutes × rate_per_minute) over every job in the pipeline
Agent CI tax (%) = (cost_with_agents − cost_without_agents) / cost_without_agents × 100
GitHub-hosted rates (standard runners):
Linux 2-core $0.006/min
Windows 2-core $0.010/min
macOS $0.062/min
Every input except the rates should come from your own run history, and the next sections show how to pull it.
Costs that don't show up as minutes
Queueing at the concurrency limit
GitHub caps concurrent jobs on standard hosted runners at 60 on the Team plan and 500 on Enterprise, and caps macOS jobs at 5 and 50 (Actions limits). On a Team plan, five agent PRs that each run one macOS job fill the macOS slots. Queued time isn't billed, but agents wait on CI results before they iterate, so a queue stalls the work the agents are supposed to speed up.
Artifact and cache churn
GitHub keeps artifacts and logs for 90 days by default, and artifact storage accrues hourly. More runs means more stored artifacts. Each repository gets 10 GB of cache storage free, with usage above that at $0.07 per GB-month. Caches keyed on a lockfile such as package-lock.json miss whenever it changes, so every agent PR that adds or bumps a dependency pays for a full reinstall, on every push.
Runs for work nobody will merge
A developer reviews an agent's PR, decides the approach is wrong, and asks the agent to start over on a new branch. The first PR stays open and its pipeline keeps running. Nobody merges that work, but its minutes are billed like any other. The concurrency setup below cancels those runs when the PR is closed.
Measure your agent share
GitHub's billing pages show totals, not which runs came from an agent. You can get that split from the REST API with gh and jq.
Export your workflow runs
The list-runs endpoint returns at most 1,000 results for a filtered query (docs), so export in windows of a week or less and append:
REPO=your-org/your-repo
gh api -X GET "repos/$REPO/actions/runs" \
-f created='2026-09-01..2026-09-07' -f per_page=100 \
--paginate \
--jq '.workflow_runs[] | {id, event, head_branch, conclusion, run_started_at}' \
>> runs.jsonl
Tag the agent branches
The hosted agents leave a branch prefix:
- Copilot's coding agent uses
copilot/(changelog). - The Claude Code GitHub Action defaults to
claude/(itsbranch_prefixinput). - Codex cloud uses
codex/.
Branches that a developer creates locally while running an agent in the terminal carry whatever name they chose, so this undercounts. If your team uses a naming convention for agent branches, add it to the pattern.
jq -s '
map(. + {agent: ((.head_branch // "") | test("^(copilot|claude|codex)/"))})
| group_by(.agent)
| map({agent: .[0].agent, runs: length})
' runs.jsonl
Turn runs into billable minutes
The per-run usage (/timing) endpoint is closing down on the new billing platform, so compute minutes from the jobs themselves. filter=all includes re-run attempts, so retries are counted. Each job is rounded up to a whole minute, the way GitHub bills it:
jq -r 'select((.head_branch // "") | test("^(copilot|claude|codex)/")) | .id' runs.jsonl |
while read -r id; do
gh api -X GET "repos/$REPO/actions/runs/$id/jobs" -f filter=all --paginate \
--jq '.jobs[] | select(.started_at and .completed_at)
| {label: (.labels | join(",")),
minutes: (((.completed_at | fromdate) - (.started_at | fromdate)) / 60 | ceil)}'
done > agent-jobs.jsonl
jq -s 'group_by(.label) | map({label: .[0].label, minutes: (map(.minutes) | add)})' agent-jobs.jsonl
Multiply each label's minutes by its rate: $0.006 for Linux 2-core, $0.010 for Windows, $0.062 for macOS, and $0.012 or $0.022 for 4-core or 8-core Linux larger runners. Run the same loop without the branch filter to get the human baseline, and check the totals against the usage report on your billing page.
Cut the waste agents create
Cancel superseded PR runs, but not runs on main
When an agent pushes three times in ten minutes, only the last push's result matters. A concurrency group per PR cancels the older runs. The group has to be scoped to PRs: a group like ci-${{ github.ref }} with cancel-in-progress: true also cancels in-progress runs on main whenever two merges land close together.
name: CI
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened, closed]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
test:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: npm ci && npm test
Pushes to a PR share the group CI-<PR number>, so each new push cancels the run still going for the previous one. Pushes to main have no PR number and fall back to github.run_id, which is unique per run, so every run on main gets its own group and nothing cancels it.
Closing a PR starts one more run in the PR's group. That run cancels whatever is still running for the PR, and its own jobs are skipped by the if, so it costs nothing. This is what stops the abandoned-PR runs from the previous section. Put the same if on every job in the workflow.
This only reaches work on the same PR. If an agent opens a new PR for a new approach, the old one keeps running until someone closes it. Closing it is enough to stop its runs.
Filter by path
Don't run the whole suite when an agent only edited docs:
on:
pull_request:
paths:
- "src/**"
- "packages/**"
- "package.json"
- ".github/workflows/**"
A workflow skipped by a path filter never reports its checks, which blocks PRs if those checks are required. The monorepo CI guide covers that case and per-package filtering.
Gate the matrix behind a fast check
Run lint and type checks first, and start the expensive jobs only when they pass:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: npm ci && npm run lint && npm run typecheck
test:
needs: check
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- run: npm ci && npm test
A failed lint run costs a few Linux minutes instead of a full matrix that includes a macOS job.
Shorten artifact retention on PRs
Agent PRs rarely need 90 days of build output:
- uses: actions/upload-artifact@v7
with:
name: build-output
path: dist/
retention-days: 3
What moving to Tenki changes
Tenki Runners run the same workflows on bare-metal 5th Gen AMD Ryzen machines for x64 and Apple Silicon M4 Pro machines for macOS. You change the runs-on label, and usage is billed per core-minute: $0.002 on x64 and $0.020 on macOS (pricing). Labels map directly to core counts (sizes), so a 2-core runner costs $0.004/min and a 2-vCPU Mac costs $0.040/min.
Here is the 20-engineer example at matching sizes:
Pipeline per push:
2× Linux, tenki-standard-small-2c-4g: 2 × 15 min × 2 cores × $0.002 = $0.12
1× macOS, tenki-macos-26-mini: 10 min × 2 cores × $0.020 = $0.40
Total per push: $0.52
With agents: 3,600 runs × $0.52 = $1,872/month in usage
Team plan, billed monthly: $250 + ($1,872 − $100 credits) = $2,022/month
GitHub-hosted: $2,880/month → $858 less (30%)
The Linux jobs are a third cheaper at the same core count ($0.004/min against $0.006/min), but the macOS job still decides the result. The macOS mini costs $0.040/min against GitHub's $0.062/min, though it has 2 vCPU and 4 GB of memory, while GitHub's standard M1 runner has 3 cores and 7 GB (GitHub-hosted runners).
If your macOS job needs the 4-vCPU size, it costs $0.080/min, which is more than GitHub charges. The pipeline then comes to $0.92 per push and $3,312 a month in usage, more than staying on GitHub. Check which macOS size your tests need before you move them. You can also move only the Linux jobs, which cost less than GitHub's rate at every matching size.
Two other Tenki differences matter for agent volume. Tenki bills per second of job runtime (limits), so short jobs aren't rounded up to whole minutes. Every job runs in a fresh VM that boots in about 15 seconds and is destroyed when the job ends.
The hardware is also faster. The example above keeps job times equal, but in our benchmarks a 4-core Tenki runner finished the n8n monorepo's full CI in 29m15s for $0.234, against 55m58s and $0.336 on GitHub's 2-core ubuntu-latest. Shorter runs also mean agents get their CI results back sooner.
On concurrency, the Team plan allows 100 concurrent sessions, shared between runner jobs and sandboxes, and 4 concurrent macOS jobs by default (macOS runners). That is more total headroom than GitHub Team's 60 jobs, but one fewer macOS slot than GitHub Team's 5. If macOS queueing is your bottleneck, ask for more macOS concurrency before you switch.
To try it, point one Linux job at tenki-standard-small-2c-4g and compare its time and cost against your ubuntu-latest run.


