GitHub Actions spend — Blacksmith trial and the credit runway

Where the CI bill goes, what Blacksmith measured on a real run, and three options for making the $9k GitHub credit last longer. For Fede and Gera.

2026-09-09 · Decided: Option A (Fede, 2026-09-09), PR #7437 closed; cuts pending approval · Trello qw8iq00P · PR #7437 held, not merged

Blacksmith does not lower the bill, it moves it from credit to cash. Its 4-vCPU machines cost the same per minute as GitHub's standard runners, both round every job up to a whole minute, and about 4,400 billed jobs a day are the problem, not the hardware. Recommendation: stay on the credit, close the PR, and cut job count.

Why this came up

At the 2026-09-08 standup Fede said GitHub Actions is costing thousands a month. Aldo (ex-colleague, at Zillow) said he went from about $300 to $80 a month on Blacksmith and that it is faster. Fede ran Blacksmith's migration wizard on 2026-09-09; it opened PR #7437 swapping the runner label in all 66 workflow files. The goal Fede stated in session: about $9,000 of GitHub credit remains, it expires in twelve months, the run rate is over $3,000 a month, and the aim is to make the credit last longer, not to hit twelve months. Cash spend on a vendor is only worth it if speed matters somewhere.

Prior work on the same bill: a self-hosted runner on the Mac mini was tried and reverted on 2026-07-31 (Fede: no self-hosted runners). The August 9 to 11 sessions targeted a 50% cut and shipped push debouncing, incremental re-review and guard consolidation; the target was not reached. On 2026-08-18 the full test suite moved to GitHub's 8-core ARM pool (PR #5856), measured then at 7 min to 2.3 min wall and $0.35 to $0.22 per run. Blacksmith itself had never been evaluated before today.

The bill, measured

Source. Every job GitHub ran between 2026-09-08 23:28 UTC and 2026-09-09 17:04 UTC, pulled from the Actions jobs API: 2,982 workflow runs, 10,752 jobs, of which 4,395 actually executed (the rest were skipped by an if: and are not billed). Priced at GitHub's published private-repo list rates per runner label, each job rounded up to a whole minute as GitHub bills, then scaled to 30 days. These are modeled figures, not the invoice; the org billing endpoint needs an admin scope this session's login lacks.

ItemPer month (modeled)ShareWhat it is
Full unit-test suite, 12 shards on the 8-core ARM pool$64723%About 57 full-suite runs a day. 54% of the shards are triggered by review verdicts on hub-module PRs, 27% by pushes to main, 17% by PR pushes that escalated to full mode.
Type check, build, lint on every PR push$43015%Three jobs per push, roughly 120 to 180 of each a day.
Affected unit tests on PR pushes$2579%One shard per push, median 56 s.
AI code review runner, parked while the model reviews$1676%Median 5.1 min, p90 8.6 min per review, on a billed machine that does nothing but wait.
Guard pins$1134%Post-merge only since 2026-08-20; still 229 jobs a day.
Everything else, about 60 small jobs$1,22543%Gates, drift checks, preview mint and remove, harnesses, evals, deploys. Most finish in under 30 s and bill one minute each.
Total$2,839100%Matches Fede's "over $3k" once storage and the review bot's own spend are added.

What Blacksmith measured on PR #7437

Source. The PR's own checks ran on Blacksmith machines because a workflow edit takes effect inside its pull request. First run at 2026-09-09 17:44 UTC, a rerun of five workflows about an hour later after the first run had warmed what it could. GitHub-side numbers are the median of the same job over the sample above, and step timings come from one representative GitHub PR run (34379781500) against the Blacksmith rerun (34384721571).

Step or jobGitHubBlacksmithRead
Type check, the CPU-bound step alone79 s35 s2.3x faster Twice the cores show up where the work is pure compute.
Lint, the CPU-bound step alone5 s7 sSame.
Hermetic harness jobs (AppFolio loop, turnover intake, app intake, portfolio), whole job116 to 174 s68 to 79 s40 to 58% faster One run each on Blacksmith; consistent direction.
Semgrep scan, whole job77 s47 s39% faster
Installing dependencies29 s cache restore90 s full npm install3x slower, every job Main seeds the node_modules cache on GitHub's cache backend. Blacksmith redirects the cache action to its own backend, so nothing seeded on GitHub is visible there. This persists until the seed job itself runs on Blacksmith, which means a full move, not a partial one.
Git checkout5 to 25 s13 to 288 sUnstable One checkout took nearly five minutes; most took 13 to 26 s.
Lint, whole job, first run49 s629 sCold lint cache plus cold dependencies. Warm rerun: 141 s, still 3x slower because of the install.
Affected unit tests, whole job109 s74 s, then 533 s on rerunInconclusive The rerun selected a different set of tests and hit the slow checkout.
Small gate jobs (nested gate, quality gate, preview mint)14 to 17 s55 to 105 sSlower These jobs are almost all install time. On GitHub the install is a 29 s restore; on Blacksmith it is a full install.

Every check that matters passed on Blacksmith: build, lint, type check, all unit test shards, the leasing regression gate, the escape gate, the preview deploys, the harnesses. The one red check, the renewal conversation scoping eval, fails identically on main today (dispatched on main at 17:56 UTC to confirm) and is a prompt problem unrelated to runners. It is not a required check.

Pricing, side by side

Source. blacksmith.sh/pricing and docs.blacksmith.sh runner overview, fetched 2026-09-09; GitHub rates from the 2026-08-18 billing pull recorded in the CI workflow file and the August sessions.

MachineGitHubBlacksmithNote
Standard x64 (GitHub 2-core vs Blacksmith 4-vCPU, the wizard's mapping)$0.008/min$0.008/minSame price, twice the cores. Blacksmith bills per vCPU: $0.004 per 2-vCPU minute.
Standard ARM (GitHub 4-core ARM vs Blacksmith 4-vCPU ARM)$0.005/min$0.005/minSame. ARM is 0.625x the x64 rate on both sides.
8-core ARM (the test-suite pool)$0.014/min$0.010/min29% cheaper per minute. This is the only line where Blacksmith's rate is lower.
Free tierTeam plan minutes, not applicable to larger runners3,000 2-vCPU x64 minutes a monthAbout $12 of value at our volume.
RoundingUp to the minuteUp to the minuteThe $800 a month of round-up moves with the jobs.

Aldo's $300 to $80 result is consistent with a small team whose jobs are compute-bound and whose GitHub bill was mostly larger-runner minutes. Our bill is thousands of one-minute jobs, where the rate is identical.

Options

OptionGitHub credit burnCashSpeedVerdict
A. Stay on GitHub, close the PR, cut job count$2,840 today, falling with each cut below$0UnchangedRecommended Every dollar stays on the credit; the cuts are the only thing that actually extends runway.
B. Full move to Blacksmith (the PR as written)$0about $2,600 modeled at the same job mix and durationsCompute steps about 2x faster; installs and checkouts slower or unstable; net wall-clock unprovenNo Trades credit for cash with no bill reduction. Also moves the cache backend, so every job re-seeds until main has run there.
C. Partial: only the 8-core test shards (narrow the PR to that one label)minus $647about $460 modeled at today's shard durationsShards likely faster once the cache is seeded on Blacksmith; unmeasured, since full mode did not run on the PROnly if speed matters Cash for credit again, and the node_modules seed would have to run on Blacksmith too or every shard pays the 90 s install.

Cuts that extend the credit (Option A)

Ranked by modeled monthly saving from the same sample. Arithmetic: jobs in the 17.6 h sample x 1.364 for a day x 30 x billed minutes x rate. All are estimates until shipped and re-measured.

CutPer monthShape
Fold the auto-merge arm-or-disarm job into a step of an already-running job$99412 one-minute jobs a day that do 8 s of work. Mechanical.
Fold the two unit-test reporter jobs into the last shard's step$112465 one-minute jobs a day, 3 to 9 s of work each. Mechanical.
Gate or retire the Quality Gate POC's three jobs$97402 one-minute jobs a day. Needs an owner's call on whether the POC is still an experiment.
Fold preview-domain mint and remove into steps$62259 one-minute jobs a day.
Stop parking a runner for the AI reviewup to $167Re-architecture: the review runs inline on a billed machine for 5 to 9 min. The August 9 session already proposed an async executor on Lambda as "Decision 3", not committed.
Full suite on review verdictsup to $35054% of full-suite shards come from review verdicts on hub PRs, by design since PR #5518 so hub changes get full coverage before merge. Cutting this is a coverage decision, not a cleanup.
Remaining sub-30-second jobs$125 to $165 realisticAbout 1,270 more tiny jobs a day across 40 workflows; maybe a third can fold or path-filter. Low confidence.

The mechanical cuts alone are about $370 a month, 13% of the bill, and take the credit from roughly three months of runway toward three and a half. Getting to a real stretch needs the two design decisions above (review parking, full suite on verdicts), which together are another $500 or so and would push runway toward five months.

Decided 2026-09-09 (Fede)

Shipped 2026-09-09

Measured after the cuts (2026-09-10)

WhatBefore the cutsAfter the cutsChange
Cost of one PR's CI run (median)$0.070$0.055-21% fewer jobs run per push (18 → 15, median), from moving the full suite off the PR path
Cost of one merge to main (median)$0.498$0.519+4%, flat the ARM savings got eaten by more of that work landing on the pricier 8-core runner (32% → 41% of merge minutes)
Jobs running per PR push (median)1815-17%
Share of all CI minutes on the old x64 machines36%12%real, clean win this is the ARM move working as intended, and it doesn't depend on how many runs happened that day
Time from a PR push to CI finishing (median, real runs only)21s24sflat
Time from a PR push to CI finishing (slow 10%)419s549sup — the PR lane's remaining work (affected tests + the merged lint/type/build job) is now a bigger share of what's left
Main went red after a merge8 of 28 pushes58 of 104 pusheslooks much worse raw, but see below

On that last row — the red-main count needs unpacking, not just quoting. Grouping the 58 red pushes into distinct incidents (not counting the same broken thing twice while it's being fixed forward) gives 14 separate episodes, not 58. Together they left main red for about 9.2 hours total across the 15.3-hour window (the longest single stretch was 1.9 hours, right after the cut shipped) — that's the time production promotes were held.

PropFlow Docs