What was actually eating our GitHub bill
Share
11 Min Read

What was actually eating our GitHub bill

GitHub Actions is the cloud bill nobody owns. It is not in your AWS account, so it slips past the FinOps review. It is priced per minute, so no single job looks expensive. And until you go looking, the organization billing page hands you a number with no cause attached to it.

Ours crept up quietly, then stopped being quiet. June 2026 came in at $984, and the four months before today added up to about $1,754. Nothing had shipped that felt like a $1,000 decision. That is the tell. When the bill jumps and no deploy explains it, the cause is usually structural, not a bad week.

This is what we found and what we did about it. The short version: just over half the problem was things we were not using at all, and the rest was a CI configuration that started a fresh army of runners on every push.

GitHub made this everyone's problem in 2026

We were not the only ones noticing. On January 1, 2026, GitHub cut GitHub-hosted runner prices by up to 39 percent, then attached a new $0.002-per-minute "Actions cloud platform charge" in the same announcement. The plan was to extend that charge to self-hosted runners on March 1. That part did not survive contact with users. Within about a week GitHub postponed the self-hosted charge, admitting it "missed the mark." Commenters in the discussion thread had calculated climbs to $140 a month for jobs running on their own hardware, and analysts like bex.co and samexpert read the retreat as a preview rather than a reversal.

The takeaway for us was not "GitHub is raising prices." It was that the cost of Actions is now something you have to actively manage, and the levers sit in your own repository.

The four things that actually make Actions expensive

Before we touched a workflow, we learned where the math bites.

  • Rounding. GitHub rounds every job up to the next whole minute. A job that runs for 20 seconds is billed as a full minute. Split your CI into twelve small jobs and you pay twelve minutes for three minutes of work. This "rounding tax" is why the number of jobs matters as much as their runtime.
  • Storage is a subscription, not a one-time cost. Artifacts are billed in gigabytes per hour, and the default retention is 90 days. An artifact from a pull request that merged in July is still on your bill in September. Delete it and future hours stop accruing, but the hours already recorded stay.
  • Runners are priced by platform. macOS is $0.062 per minute, roughly ten times Linux. Windows is $0.010. A desktop build on macOS costs what a lot of Linux CI costs for a whole day.
  • The control plane never goes away. Even self-hosted runners hand scheduling to GitHub. As bex.co put it, self-hosting removes the compute bill, not the dependency.

How we found the money

We started from the billing export, not from the workflows. GitHub's enhanced billing API returns one row per product, SKU, and repository, which is exactly the granularity you need.

gh api "/organizations/<org-id>/settings/billing/usage?year=2026&month=6" \
  --jq '.usageItems[] | [.sku,.netAmount] | @tsv' \
  | awk -F'\t' '{s[$1]+=$2} END{for(k in s) printf "$%.2f  %s\n", s[k], k}' \
  | sort -rn

Aggregated across May through October, the shape was not what we expected:

  • Actions Linux minutes: $1,532
  • Actions storage: $1,101
  • GHAS Code Security: $186
  • Actions macOS: $167
  • GHAS Secret Protection: $118
  • Code Quality: $64
  • Actions Windows: $44
Horizontal bar chart of May through October 2026 GitHub Actions spend by SKU, led by Linux minutes and Actions storage.
May–October 2026 spend by SKU. Actions Linux minutes and storage accounted for most of the bill.

Storage was the second biggest line, and we had never once thought about it. So we asked the obvious question: which repository is holding all of this?

gh api --paginate "/repos/$ORG/$REPO/actions/artifacts?per_page=100" \
  --jq '.artifacts[] | select(.expired==false) | .size_in_bytes' \
  | awk '{t+=$1} END{printf "billable: %.2f GB\n", t/1e9}

The answer was a repository we were not using at all. It had accumulated gigabytes of build artifacts from desktop and release workflows, plus the macOS and Windows runners used to produce them. That single repository was the storage line and most of the platform-multiplied minutes. The pattern is common enough that other teams have written up the same discovery: a build artifact uploaded on every run with no retention, quietly becoming the bill.

Two more things fell out of the audit. GitHub Advanced Security was enabled across the organization, and GitHub's newer Code Quality product was on. Neither was part of a decision anyone remembered making.

What we changed

1. Killed the repository we were not using. We deleted every artifact, then archived the repository. Deleting is what stops the meter. Archiving alone leaves the stored artifacts billing until they expire.

gh api --paginate "/repos/$ORG/$REPO/actions/artifacts?per_page=100" --jq '.artifacts[].id' \
  | xargs -I{} gh api -X DELETE "/repos/$ORG/$REPO/actions/artifacts/{}"
gh api -X PATCH "/repos/$ORG/$REPO" -f archived=true

2. Turned off the security products we were paying for but not using. Advanced Security is billed per active committer, and we already run gitleaks in pre-commit, so Secret Protection was redundant. We applied a security configuration with the paid products disabled to every repository, instead of clicking through them one at a time.

gh api -X POST /orgs/$ORG/code-security/configurations \
  -f name="Cost control: no paid security products" \
  -f secret_scanning=disabled \
  -f secret_scanning_push_protection=disabled \
  -f code_scanning_default_setup=disabled \
  -f dependency_graph=enabled -f dependabot_alerts=enabled \
  -f enforcement=enforced
gh api -X POST "/orgs/$ORG/code-security/configurations/$ID/attach" -f scope=all

We kept the free pieces, the dependency graph and Dependabot alerts. Code Quality has no API, so that one was a single toggle in organization settings.

3. Fixed the CI configuration. The platform repository had twelve separate path filters, each inside its own job, so a docs-only pull request still started a dozen runners just to check whether they could exit. We replaced them with a single change-detection job and gated every other job on its output. A job skipped by a condition reports success, which keeps the required check green. A workflow skipped by a path filter leaves the check pending and blocks the pull request. We also turned on cancel-in-progress so a follow-up push cancels the run it replaces, put a label gate in front of a 45-minute container build that had been running on every platform PR, and dropped artifact retention to seven days.

concurrency:
  group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name != 'merge_group' }}

4. Fixed the budget that would have bitten us. Our Actions budget was set to $200 with "stop usage" enabled, while our normal burn was closer to $465 a month. That combination does not cap spending. It shuts CI off partway through the month. We raised it above expected burn until the reductions settle, then we will lower it again.

Where the money goes now

Just over half the problem was things we were not using. Removing them, plus the CI changes, takes our run rate from roughly $535 a month to somewhere around $130 to $180, which is $4,000 to $5,000 a year. The Actions-Linux line is the one estimate left, since it depends on how much the new gating saves over a full month. Everything else is a hard zero.

What we would tell the next team

Four things earned their keep, and none of them needed a new tool.

  • Look at the bill before the workflow. The billing API hands you the SKU and the repository in one call. The workflow only tells you what you intended.
  • Storage is the line everyone forgets. Set retention explicitly, and never use artifacts as a CDN.
  • Count your jobs, not just your minutes. Rounding makes a wide, shallow pipeline expensive even when it is fast.
  • Set the budget above your real burn. A cap below normal usage is an outage waiting in the middle of the month.

The next move is to hand the heavy builds to Cloudflare, where Pages and Workers Builds price per build instead of per minute, and where release artifacts can live in R2 rather than GitHub storage. That is a migration, not a toggle, but it is the same lesson as this one. The moment you can see the cost, you can move it.

Numbers come from our own organization's billing usage API for February through October 2026. The Actions-Linux reduction is an estimate until we have a full post-change month.

Put this into practice

Bring one of these ideas to your own data

We scope a single use case and show you how it works, in weeks. Here is the shape of that work for a team like yours.

Book a scoping callSee pricing →

Put the idea to work

Turn what you learned into a practical next step.

We can help you identify the right starting point, scope the work, and ship something useful without committing to a large transformation first.

AI Readiness Report
A clear breakdown of what Brainforge fixes, how fast, and what it actually delivers.
AI Readiness Report

Get the best insights right at your inbox.

A clear breakdown of what Brainforge fixes, how fast, and what it actually delivers.

No fluff. Just clarity.
Green spiral lines

Press Esc to close