Re-run Storms and What They Cost
A re-run storm bills the same commit twice or three times over. Price the re-run rate against monthly minutes, then cut it with cancellation and retries.
Last verified:
A re-run storm is the pattern where a large share of your GitHub Actions minutes goes to running commits that already ran, and its cost is the re-run rate multiplied by the billed minutes per re-run and the per-minute rate. On 700 first-attempt runs a month of a 6-job matrix that bills 54 minutes per run, a 25 percent re-run rate adds 9,450 billed minutes, which is $151.20 at the warp-ubuntu-latest-x64-8x rate of $0.016 per minute from the WarpBuild pricing page, checked on 2026-08-13.
This guide covers the three causes worth separating, the measurement that turns a complaint into a rate, the fixes in the order that pays best, and a model that prices each fix against monthly minutes.
Diagnosis
Measure the rate before changing a workflow, because the three causes take different fixes and two of them look identical in a job log.
The re-run rate is the count of workflow run attempts beyond the first, divided by the count of first attempts, over a fixed window. GitHub's workflow runs REST API returns a run_attempt field on every run, so counting the runs where run_attempt is greater than 1 gives the numerator directly (GitHub REST API for workflow runs). The billed side comes from the CI billing tab in WarpBuild Reports, which is one row per job execution carrying repository, job name, runner label, stack, execution time, billed time, and cost, and exports to CSV with every row matching the current filters.
The three causes
| Cause | What you see | What confirms it |
|---|---|---|
| Flaky tests | The same commit goes red then green with no code change, and the failing test name moves between attempts | Success rate under 98 percent on the Jobs report row with CPU P90 and memory P90 both low |
| Transient infrastructure failure | Registry 429 or 502, DNS failure, a checkout that times out, a package download that resets | Several unrelated jobs go red inside the same ten minute window, then everything recovers |
| Required checks that re-run the whole matrix | One shard fails and every job in the matrix runs again on attempt 2 | The job list on attempt 2 has the same length as attempt 1 |
Flaky tests produce re-runs one commit at a time and are the cause most teams name first. The Jobs section of Reports aggregates per unique repository, workflow, and job name and carries run count, success rate, duration percentiles, queue time percentiles, and CPU and memory percentiles, which is enough to sort the unstable jobs from the merely failing ones.
Transient infrastructure failures cluster in time rather than by test. They are cheap to absorb with a retry on the one step that touches the network and expensive to absorb by pressing re-run on the whole workflow.
Required checks that re-run the whole matrix are the multiplier on both of the others. GitHub offers re-running a full workflow run, re-running all failed jobs, or re-running a specific job, each available for up to 30 days after the initial run (re-running workflows and jobs). A team in the habit of pressing the first option pays the width of the matrix on every flake.
Fix
Apply these in order. The first cuts baseline minutes, the second and third cut the re-run rate.
1. Cancel superseded runs. A concurrency group keyed on the workflow and the ref with cancel-in-progress: true stops the previous attempt as soon as a new commit lands on the same pull request (workflow syntax for concurrency). This is the largest single reduction on a busy repository because a developer pushing three fixup commits in ten minutes otherwise pays for three full matrices. Cancelled jobs bill for the time they ran, which is covered in how a cancelled GitHub Actions job is billed.
2. Retry the step, not the workflow. A network install, a registry login, or an artifact download is worth two or three attempts inside the step itself, with a wait between them. Retrying at that level costs seconds and keeps the other five shards from running a second time. See how to retry a failed step in GitHub Actions for the step-level and job-level options.
3. Quarantine known-flaky tests. Move the tests with a documented history of instability into a list the gate skips and a separate job runs with continue-on-error: true. The gate stops going red for a known reason, the quarantined set stays visible, and the diagnosis work continues in finding and fixing flaky GitHub Actions jobs.
Configuration
name: pr-gate
on:
pull_request:
concurrency:
group: pr-gate-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
test:
runs-on: warp-ubuntu-latest-x64-8x
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
uses: nick-fields/retry@v3
with:
max_attempts: 3
retry_wait_seconds: 15
timeout_minutes: 10
command: npm ci
- name: Run shard
run: npm test -- --shard=${{ matrix.shard }}/6 --testPathIgnorePatterns "$(paste -sd '|' - < quarantine.txt)"
quarantined:
runs-on: warp-ubuntu-latest-x64-2x
timeout-minutes: 20
continue-on-error: true
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test -- --testPathPattern "$(paste -sd '|' - < quarantine.txt)"Guarding cancel-in-progress on the event name keeps pushes to the default branch running to completion while pull request attempts get cancelled, which is what you want when the default branch feeds deployments.
The fail-fast: false line looks like it raises the bill and lowers it in practice. With fail-fast: true the first red shard cancels its five siblings, so the run reports one failure and nothing about the other five, and the next step is a full re-run of the matrix. With it set to false every shard reports, and one press of re-run failed jobs settles the run.
Sizes and labels for the runs-on line are in the cloud runners catalog. A matrix spanning platforms prices each row from the same table.
Cost or Time Model
Take one pull request gate: a 6-job matrix, 9 billed minutes per job, so 54 billed minutes per run, at 700 first-attempt runs a month on warp-ubuntu-latest-x64-8x. Baseline is 700 x 54 = 37,800 minutes, which is $604.80 at $0.016 per minute.
| Re-run rate | Full-matrix re-runs per month | Extra billed minutes | Extra cost per month |
|---|---|---|---|
| 5 percent | 35 | 1,890 | $30.24 |
| 15 percent | 105 | 5,670 | $90.72 |
| 25 percent | 175 | 9,450 | $151.20 |
| 40 percent | 280 | 15,120 | $241.92 |
The arithmetic on the 25 percent row: 700 x 0.25 = 175 re-runs, 175 x 54 = 9,450 minutes, 9,450 x $0.016 = $151.20.
Now price the three fixes against that starting point of 47,250 billed minutes, or $756.00 a month.
Cancellation. Assume 210 of the 700 runs are superseded by a newer push. Running to completion they bill 210 x 54 = 11,340 minutes, or $181.44. Cancelled after about 2 minutes per job they bill 210 x 12 = 2,520 minutes, or $40.32. The difference is 8,820 minutes and $141.12.
Targeted re-runs. Re-running only the failed shard bills 9 minutes rather than 54. The same 175 re-runs cost 175 x 9 = 1,575 minutes, or $25.20, against $151.20. The difference is $126.00.
Quarantine. Moving the known-flaky set out of the gate takes the rate from 25 percent to 15 percent, so 105 targeted re-runs bill 945 minutes, or $15.12.
All three together: 37,800 minus 8,820 plus 945 = 29,925 billed minutes, or $478.80 a month, against $756.00. That is 17,325 minutes and $277.20 a month, and the engineer-time column moves further: 175 re-runs at 9 minutes of waiting plus 5 minutes of context switching is 40.8 hours a month, and 105 of them is 24.5 hours.
The rate itself is the reason the arithmetic is worth doing twice a year. warp-ubuntu-latest-x64-8x (8 vCPU, 32 GB) costs $0.016 per minute against $0.022 per minute for the 8-core Linux larger runner (8 vCPU, 32 GB): 27 percent lower list price (GitHub Actions minute multipliers, checked on 2026-08-13). Applied to the 47,250 minute month above, the same workload sits at $1,039.50 on the GitHub-hosted size.
Pricing is purely usage based. There is no base subscription fee, no platform fee, and no seat fee, so every minute a re-run stops consuming leaves the invoice at the next billing period rather than at the next contract date. Signup includes $10 free credits, which covers a first month of measurement on one workflow, and full rates are on the pricing page.
For the per-pull-request view that this model rolls into, see GitHub Actions cost per pull request. For the flake work that lowers the rate, start at finding and fixing flaky GitHub Actions jobs.
FAQ
How do I calculate what re-runs cost per month?
Multiply first-attempt runs by the re-run rate, multiply that by the billed minutes a re-run consumes, and multiply again by the per-minute rate. On 700 first-attempt runs of a 6-job matrix billing 54 minutes per run, a 25 percent re-run rate is 175 re-runs, 9,450 billed minutes, and $151.20 at the warp-ubuntu-latest-x64-8x rate of $0.016 per minute, checked on 2026-08-13.
Does re-running failed jobs cost less than re-running all jobs?
Yes, by the width of the matrix. Re-run failed jobs re-runs only the failed jobs and their dependents, so one failed shard out of six bills 9 minutes instead of the 54 that a full re-run bills. GitHub allows both up to 30 days after the initial run.
Do cancelled jobs still bill minutes?
They bill for the time they actually ran before the cancellation landed, so cancel-in-progress converts a superseded 9 minute job into roughly a 2 minute one rather than into nothing. That is still the largest single reduction available on a busy pull request.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.