Skip to content

Repository files navigation

ci-cost

Find out what your CI actually costs — and what it would cost somewhere else.

License: MIT Python 3.9+ Dependencies: none Vendors: 8

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: cross-vendor CI cost comparison

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.

Using it with ci-speedup and ci-score

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.


Table of contents

What a report looks like

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.

Why the "cheapest" column lies

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_like per 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.

Quickstart

npx skills add NWelde/ci-cost

Select 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-cost

First run

Requirements:

  • An authenticated GitHub CLI (gh auth login), or a GITHUB_TOKEN / GH_TOKEN in the environment. Read access to the repo's Actions data is enough. An environment token wins over gh when 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.mdfacebook/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.

How it works

                    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
  1. Sample. pull_github_data.py walks 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 flags low_confidence when the observed history is too short to trust.
  2. Price. build_comparison.py runs 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.
  3. 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.

Vendors covered

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.

About the rates

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.

What it deliberately does not do

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.

Using the scripts without an agent

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.json

Step 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**

Troubleshooting

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.

Roadmap

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.

Learn more

Contributing a vendor rate

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.

Repository layout

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.

Development

python3 -m pytest tests/ -q                                    # 588 tests, no build step
uv run --python 3.9 --with pytest python -m pytest tests/ -q   # the CI floor

CI 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.

License

MIT © Nathan Beyene

About

Agent skill that measures a repo's real GitHub Actions usage and prices it against 8 CI vendors

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages