# The hidden CI tax of AI coding agents (https://tenki.cloud/blog/hidden-ci-tax-ai-coding-agents)

> For the complete documentation index, see [llms.txt](https://tenki.cloud/llms.txt)

- Author: Eddie Wang (Engineering)
- Published: Apr 8, 2026
- Updated: Sep 22, 2026
- Category: [CI/CD](https://tenki.cloud/blog/category/ci-cd)
- Reading time: 10 min

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.

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](https://github.blog/news-insights/octoverse/octoverse-a-new-developer-joins-github-every-second-as-ai-leads-typescript-to-1/) 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](https://tenki.cloud/blog/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.

```text
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.

```text
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](https://docs.github.com/en/billing/concepts/product-billing/github-actions)), so most of this volume is billed either way. GitHub also [rounds each job up to the next whole minute](https://docs.github.com/en/billing/reference/actions-runner-pricing), so many short jobs cost more than their runtime suggests.

## The cost formula

```text
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](https://docs.github.com/en/actions/reference/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](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/remove-workflow-artifacts), 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](https://docs.github.com/en/rest/actions/workflow-runs)), so export in windows of a week or less and append:

```bash
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](https://github.blog/changelog/2025-10-16-copilot-coding-agent-uses-better-branch-names-and-pull-request-titles/)).
* The Claude Code GitHub Action defaults to `claude/` (its `branch_prefix` input).
* 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.

```bash
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](https://github.blog/changelog/2025-02-02-actions-get-workflow-usage-and-get-workflow-run-usage-endpoints-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:

```bash
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](https://docs.github.com/en/billing/reference/actions-runner-pricing). 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.

```yaml
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:

```yaml
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](https://tenki.cloud/blog/monorepo-ci-github-actions-selective-builds) 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:

```yaml
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:

```yaml
- uses: actions/upload-artifact@v7
  with:
    name: build-output
    path: dist/
    retention-days: 3
```

## What moving to Tenki changes

[Tenki Runners](https://tenki.cloud/products/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](https://tenki.cloud/docs/pricing.md)). Labels map directly to core counts ([sizes](https://tenki.cloud/docs/runners/sizes.md)), 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:

```text
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](https://docs.github.com/en/actions/reference/runners/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](https://tenki.cloud/docs/runners/limits-and-concurrency.md)), 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](https://tenki.cloud/docs/runners/benchmarks.md) 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](https://tenki.cloud/docs/runners/macos-runners.md)). 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.