Find out what your CI actually costs — and what it would cost somewhere else.
An independent, vendor-agnostic cost approximator for CI. It answers the two questions a billing page cannot: what would this same workload cost on a different vendor, and what is a CI speed improvement worth in dollars.
ci-cost samples a repository's real GitHub Actions run history over the gh
API, totals the measured minutes per runner class, and replays that one usage
profile through 8 vendors' pricing mechanics — per-minute, credit-based, and
self-hosted compute estimate. Every vendor is priced from the same measured
minutes, so a comparison varies by pricing model and never by workload.
It is not a billing tool. GitHub's billing page and usage export already
report exactly what you were charged, and for that question they are the source
of truth. ci-cost samples and extrapolates, which is an approximation of a
number you can look up. The part with no substitute is the cross-vendor replay.
Three flags mark a total you should not read at face value, because the cheapest-looking row is usually cheapest for a reason that isn't price:
is_like_for_like: false— the vendor cannot run part of the workload, so its total silently excludes those minutes. A Linux-only host priced against an iOS repo drops every macOS minute and sorts to the top while being unable to build the app. Such a vendor is reported outside the ranking, with what those minutes cost you today.has_unmodeled_fees: true— a fixed fee exists that cannot be stated as one number, so the total is a floor. RunsOn is the live case: its per-minute rates are an AWS pass-through with no markup, while a tiered annual license fee in euros sits outside the figure. Priceable fees are included — Depot's $20/mo plan base is in its total, and totals are reported split as usage plus base so a fee is visible rather than inferred.has_uncertain_rates: true— whoever authored the entry was not confident about a claim and recorded why. This one cuts both ways: a beta rate may rise, but an uncertain unavailable mapping means the vendor may run more than its exclusions say, understating it.
Repository visibility is priced too — GitHub does not bill standard runners on public repos, so a "saving" there can be negative. Unrecognized runner labels fail loudly rather than being guessed at a plausible rate.
Vendor-agnostic on purpose. ci-cost is not published by a CI vendor and has
no preferred answer. Every vendor is priced by the same rules and subject to the
same guardrails, including the one whose ecosystem this is built for.
Built to sit alongside StarSling's CI skills in
starslingdev/skills, and deliberately
not overlapping them:
ci-score
grades CI configuration;
ci-speedup
finds what makes CI slow; ci-cost prices it. The routing is mutual — each
skill's frontmatter names the others — and nothing is coupled.
ci-speedup already reports findings in runner-minutes saved, and ci-cost
is the only one of the three that turns minutes into dollars. That makes the
handoff direct in both directions. Before an optimization pass, ci-cost
ranks the workload by spend rather than duration: macOS bills at $0.062/min
against Linux's $0.006, so the slowest job is frequently not the costliest one.
After a pass, it prices the minutes that went away — "we cut runner-minutes
40%" is an engineering result; "that's $180/month" is the version that survives
contact with a budget.
- What a report looks like
- Why the "cheapest" column lies
- Quickstart
- How it works
- Vendors covered
- About the rates
- What it deliberately does not do
- Using the scripts without an agent
- Troubleshooting
- Roadmap
- Contributing a vendor rate
- Repository layout
- Development
Ask your agent for a CI cost audit and you get ci-cost-report.md. Here's one
for a repo running ~5,200 Linux minutes and ~300 Windows minutes a month:
You are spending $34.20/month on GitHub Actions.
The cheapest alternative that can run your entire workload is RunsOn at $5.80/month — though that is a self-hosted compute estimate, so it is AWS spot pricing plus a licence, not a managed per-minute rate. The cheapest managed per-minute options that cover the whole workload are Blacksmith and WarpBuild, both at $23.20/month — a saving of $11.00/month ($132.00/year).
Vendor Monthly cost Pricing model Plan tier assumed Snapshot runs-on $5.80 self-hosted compute estimate Commercial Starter 2026-08-04 blacksmith $23.20 per-minute by runner class Pay-as-you-go 2026-08-04 warpbuild $23.20 per-minute by runner class Cloud Runners 2026-08-04 github-actions (current) $34.20 per-minute by runner class Team 2026-08-02 gitlab-ci $55.00 per-minute by runner class SaaS compute-minutes add-on 2026-08-03 Vendors that cannot run your full workload — listed separately, not ranked:
Vendor Partial cost Cannot run Costs you today ubicloud $8.32 300 windows-x64 min/mo $3.00/mo depot $20.80 300 windows-x64 min/mo $3.00/mo starsling $20.80 300 windows-x64 min/mo $3.00/mo Rates are catalog
0.3.0, snapshotted 2026-08-02 to 2026-08-04. Verify against each vendor's current pricing page before acting on these numbers.
Every figure above is produced by a Python script reading a rate table. The agent chooses what to say about them; it never computes or adjusts one.
This is the problem ci-cost exists to solve.
Vendors differ in what they offer, not just what they charge. Price an iOS repo against a Linux-only vendor and its total silently excludes every macOS minute — so it comes out smallest, sorts to the top, and reads as the bargain, while being unable to build the app at all.
A real run: for a repo with 5,200 Linux and 640 macOS minutes a month, one Linux-only vendor scores $5.20/mo against GitHub Actions' $70.88 — a 93% "saving" that is entirely an artifact of the 640 macOS minutes it cannot run.
ci-cost treats this as a first-class concept rather than a footnote:
- The comparison payload carries
is_like_for_likeper vendor. - A vendor that fails it can never be presented as a cheaper alternative — it goes in its own section, outside the ranking.
- The report names exactly what it can't run and what those minutes cost you today, because "excludes 640 macOS minutes" only becomes legible as "$39.68/month".
- A vendor whose runner classes nobody has mapped yet is a hard error, not a silent zero. Confusing "verified: no equivalent" with "not mapped yet" is the one failure nothing downstream could detect.
If nothing is cheaper, the report says so plainly. An audit that finds no saving is a result, not a failure.
npx skills add NWelde/ci-costSelect your agent (Claude Code, Codex, Cursor, …) and an install scope. The
skills CLI (built by Vercel) is
fetched fresh via npx, so you always get its latest version. Reinstall to pick
up a newer pricing catalog.
Then, from inside the repository you want to audit, either ask your agent in plain language —
what does CI cost for this repo, and would another vendor be cheaper?
— or invoke the skill directly:
# Claude Code
/ci-costRequirements:
- An authenticated GitHub CLI (
gh auth login), or aGITHUB_TOKEN/GH_TOKENin the environment. Read access to the repo's Actions data is enough. An environment token wins overghwhen both are present. python3(3.9 or newer). Every script is stdlib-only — nothing to install.
What it does: it asks which repository to audit and how much history to
sample, sizes the run cap to that repo's own activity, then samples run history,
prices it, and writes a report to ./ci-cost-report.md in your working
directory. It tells you what cap it used and whether the sample actually reached
as far back as the window you asked for. Nothing leaves your machine except the
GitHub API calls for the repo being audited.
Every figure carries its basis. Only the measured minutes are observed; monthly minutes are those times an extrapolation factor, and every dollar is those minutes times a catalog rate. A report states the multiplier next to the figure rather than in a closing caveat, and says so in the lead line when the sample is under a week.
A worked example is committed at
examples/react-audit.md — facebook/react, catalog
v0.3.0: 4,712.9 minutes measured over 1.31 days, ×22.9 → 108,071 min/month,
modeling to $0.00 on GitHub Actions because the repo is public, or
$648.43/mo had it been private.
GitHub REST API
│
pull_github_data.py ← measures
│
usage-profile.json per-runner-class
│ minutes/month
│
build_comparison.py ← prices
│
comparison.json every vendor,
│ deterministically
│
skills/ci-cost/SKILL.md ← explains
│
ci-cost-report.md what matters
for this repo
- Sample.
pull_github_data.pywalks workflow runs and jobs over the GitHub REST API, classifies each job's runner labels into a runner class (linux-x64-default,macos-4-core,windows-arm64, …), and extrapolates the sampled minutes to a monthly figure per class. It flagslow_confidencewhen the observed history is too short to trust. - Price.
build_comparison.pyruns that profile through one pure pricing function per archetype — per-minute, credit-based, self-hosted compute estimate — for every vendor in the catalog, and emits facts as JSON. It takes no position on ranking. - Explain. The skill reads those facts and writes the report: what you spend, what's genuinely cheaper, what can't run your workload, what couldn't be priced and why, and the provenance of every rate.
The scripts compute every number; the agent explains what they mean. That boundary is the whole design. Cost math is deterministic because nothing in the computation path is an LLM. What needs judgment is relevance — a repo with no macOS jobs doesn't need to hear that a vendor lacks macOS runners; a repo where macOS is 40% of spend needs that stated first. A fixed report template gets one of those two cases wrong every time.
See ARCHITECTURE.md for the full picture, including where
an LLM is and isn't allowed to appear.
Catalog 0.3.0, snapshotted 2026-08-02 → 2026-08-04.
| Vendor | Pricing model | Role |
|---|---|---|
| GitHub Actions | per-minute by runner class | usage source + replay target |
| GitLab CI | per-minute by runner class | replay target |
| Blacksmith | per-minute by runner class | replay target |
| WarpBuild | per-minute by runner class | replay target |
| Depot | per-minute by runner class | replay target |
| Ubicloud | per-minute by runner class | replay target |
| Starsling | per-minute by runner class | replay target |
| RunsOn | self-hosted compute estimate | replay target |
Missing one? Contributing a vendor rate is a contained pull request to the catalog, not a code change.
Vendor pricing ships as a frozen, versioned snapshot
(skills/ci-cost/references/pricing-catalog.json), never fetched live. That is
what makes the arithmetic reproducible: a pinned catalog version plus a usage
profile always produce the same figures, and every number traces to a catalog
entry. Determinism is a claim about the math, not about the rates — and not about
the prose, which an agent writes.
The trade is that rates go stale. Every report prints its catalog version and per-vendor snapshot dates, and tells you to verify against the vendor's current pricing page before acting on any number. Every rate is hand-verified against the live page before it lands, mapped to that vendor's own equivalent, with a source quote recorded behind it — some are only visible behind a vCPU slider or a client-side tab and were read off by hand.
Catalog v0.3.0 prices github-actions, gitlab-ci, starsling,
warpbuild, ubicloud, depot, blacksmith and runs-on.
Out of scope: enterprise or negotiated pricing; per-seat vendors (a seat cost
is not a function of measured usage); multi-repo aggregation; migration effort;
and subtracting a plan's included minutes, which belong to the whole account
rather than one repo and are disclosed instead. ci-cost ranks on modeled cost
alone — speed, reliability, support and coverage breadth sit outside the model,
and any of them can outweigh the dollar figure.
- It does not tell you to migrate. It reports the cost difference. Whether a saving justifies changing CI provider depends on things a tool can't see.
- It does not estimate migration effort.
- It does not cover enterprise or negotiated pricing — only public, standard pay-as-you-go tiers, which are the only rates publicly knowable.
- It does not aggregate across repos. One repository per audit.
- It does not price per-seat vendors. A per-seat cost depends on team size, which measured usage cannot observe — so the catalog rejects that pricing model outright rather than letting an uncomputable entry pass validation.
- It does not make CI faster. This is a cost tool, not a performance tool.
The scripts are the product; the skill is the narrator. Everything below runs standalone, with no LLM present — which is exactly how the math stays verifiable.
SKILL=skills/ci-cost
# 1. Measure 30 days of real usage
python3 $SKILL/scripts/pull_github_data.py acme/api \
--window-days 30 --output usage-profile.json
# 2. Price it against every vendor (JSON facts)
python3 $SKILL/scripts/build_comparison.py usage-profile.json \
--output comparison.json
# 3. Or just render a single-vendor markdown report
python3 $SKILL/scripts/compute_current_vendor_cost.py usage-profile.jsonStep 3 prints a complete report on its own:
# ci-cost report
Generated: 2026-08-05
Vendor: github-actions
Repo: acme/api
Pricing catalog version: 0.3.0, snapshot: 2026-08-02 — verify against the
vendor's current pricing page before acting on these numbers.
| Runner class | Monthly minutes | Rate ($/min) | Monthly cost |
| --- | --- | --- | --- |
| linux-x64-default | 5200.00 | $0.0060 | $31.2000 |
| windows-x64 | 300.00 | $0.0100 | $3.0000 |
**Total estimated monthly cost: $34.20**UnrecognizedRunnerLabelError — an unknown runner label
Your repo uses a custom label (say ubuntu-latest-16-core-oss) that isn't a
stock GitHub-hosted runner name. Nothing guesses what it maps to: a wrong guess
would misprice every future audit silently.
The agent asks you which known runner class it actually is, and on your
confirmation writes {label: runner_class} into
skills/ci-cost/references/runner-label-aliases.json so the same repo never
asks twice. That file is gitignored — it's a fact about your CI naming, not
about vendor pricing.
If the label is genuinely self-hosted or corresponds to no GitHub-hosted SKU, the audit stops rather than inventing a price for something GitHub doesn't sell.
low_confidence: true in the output
The earliest sampled run was under a day old — so extrapolating it to a
monthly figure isn't reliable. Note that a wide window on a quiet repo can
still come back low_confidence, and that a wide window (say 300 days) is
still capped at 200 runs by default, so a busy repo may not actually get that
much history sampled. Reports surface both caveats.
A vendor shows up under could_not_price
Two causes, both reported by name with a reason rather than dropped silently:
unmapped_runner_class— the vendor has no catalog entry for a runner class you actually use. The missing classes are listed; they need adding to the catalog.unsupported_pricing_model— the vendor's pricing mechanics can't be modeled yet (per-seat pricing, for instance).
The agent will never fill in a missing rate from memory. That confidently-wrong number is the exact failure this tool exists to avoid.
No measured minutes at all
Check that gh auth status succeeds, or that GITHUB_TOKEN / GH_TOKEN is
set with read access to the repo. Then widen the window — a repo with no runs
in the sampled period has no measurable cost.
v1 measures usage from GitHub Actions only; every other vendor is a replay target.
v2 aims to make that symmetric: sampling usage directly from other CI providers — GitLab CI, CircleCI — as a source, not just a replay target, so the audit works for repos that don't run on GitHub Actions at all.
Also planned: importing a GitHub usage-export CSV instead of sampling, which would replace the extrapolation with exact billed minutes.
skills/ci-cost/SKILL.md: the full skill contract — what the agent may and may not do with a computed figure.ARCHITECTURE.md: how the pieces fit and exactly where the deterministic/LLM boundary sits. The cost path is plain Python; the agent layer only interprets facts already computed and never produces or adjusts a number.skills/ci-cost/references/pricing-catalog.json: the versioned rate catalog itself.examples/react-audit.md: a real report, unedited.CHANGELOG.md: the decision history, including which earlier design decisions were reversed and why.
Rates are hand-verified against the vendor's live pricing page before they land.
The add-vendor maintainer skill scrapes a page and proposes a mapping from the
vendor's own machine classes to ci-cost runner classes — but it writes a
draft and opens a PR carrying the source quote behind every rate. It never
edits the catalog directly. A number reaches the catalog only after a person has
checked it.
python3 maintainers/ci-cost/scripts/validate_vendor_draft.py <draft.json>
python3 maintainers/ci-cost/scripts/open_vendor_pr.py <draft.json>validate_vendor_draft.py gates the draft with the real catalog validator, so
a passing draft cannot fail once merged.
Bug reports and pull requests are welcome. If you're changing behaviour, the
CHANGELOG.md records decisions and why they changed — please
add yours.
skills/ci-cost/ the installable skill — this is what ships
SKILL.md
scripts/ stdlib-only, runnable and tested without an LLM
references/ the versioned pricing catalog
maintainers/ci-cost/ catalog authoring; never copied into an install
SKILL.md the add-vendor skill
scripts/ scraping, draft validation, catalog PRs
tests/ flat, one test module per feature
Everything under maintainers/ is for whoever keeps the pricing catalog
current, and is deliberately excluded from what users install —
open_vendor_pr.py opens pull requests against this repo, which would make
no sense on yours.
python3 -m pytest tests/ -q # 588 tests, no build step
uv run --python 3.9 --with pytest python -m pytest tests/ -q # the CI floorCI runs the suite on Python 3.9 and 3.13 plus a catalog-validation job, on every
push to master and every pull request
(.github/workflows/tests.yml). Fork pull
requests require maintainer approval before any workflow runs.
No dependencies to install. If the tests pass, the cost math is correct — that is the entire point of keeping an LLM out of the computation path.
MIT © Nathan Beyene