Repository navigation
Warn before the Actions minute budget runs out #126
Description
What to build
The organization ran out of Actions minutes on 2026-08-27 with no warning at all. Every private repo went dark mid-working-session — runs allocated nothing and failed in three seconds — and the first anyone knew of it was workflows failing for reasons that looked like broken code.
Nothing watches the spend. Add a watchdog that warns while there is still room to act.
It reads the org's Actions consumption, compares it against the included allowance, and raises a visible alert as the month's usage crosses thresholds — early enough that a busy week can be noticed before it becomes an outage. Run it on a schedule from a public repo, where standard runners are free, so the watchdog itself never contributes to the problem it watches.
The billing API moved and the old endpoint is gone. /orgs/{org}/settings/billing/actions now returns 410 This endpoint has been moved. The enhanced billing endpoint at /organizations/{org}/settings/billing/usage works and — usefully — needs no admin:org scope, only an ordinary token. It accepts year and month, and returns per-repo line items carrying product, sku, unitType, quantity and netAmount. Confirmed working against this org while writing this ticket.
Two traps that will otherwise produce a watchdog that reads plausibly and reports nonsense:
- The response covers public and private repos together, and public usage is fully discounted rather than absent. Filter on what actually consumes the allowance, or the number will read roughly five times too high.
- The allowance is consumed in billable minutes, not wall-clock minutes. GitHub rounds every job up to a full minute, so 561 minutes of real compute billed 2,845. A watchdog reasoning about run durations will consistently under-report.
Alert somewhere a human sees without looking — the estate already has a Discord webhook path for this.
Acceptance criteria
- Current month private-repo Actions consumption is reported against the allowance
- Crossing a threshold produces an alert a human receives without going looking for it
- The watchdog runs from a public repo and its own runs are billed as free
- Public-repo usage is excluded from the figure compared against the allowance
- The threshold alert is verified by forcing it to fire — a watchdog whose alarm has never been observed to trigger is not known to work
- The figure it reports is reconciled once against the billing UI, so a wrong-by-a-factor reading is caught immediately rather than at the next outage
Blocked by
- None — can start immediately.
Activity
feat(budget): warn before the org runs out of Actions minutes (#128)
feat(budget): schedule the Actions budget watchdog from a public runn…
fix(budget): pass the watchdog thresholds as numbers, not expressions (…
fix(budget): request the billing permission and say so when it is mis…
fix(budget): make the billing permission opt-in so it degrades instea…
ExtraToast commentedon Aug 29, 2026
Built, merged, and running — but not yet watching. One owner action needed.
Reusable workflow in #128, scheduled caller in JorisJonkers-dev/.github#43, plus two follow-up fixes (#130, #131) and a caller fix (JorisJonkers-dev/.github#44).
The arithmetic is verified against live billing data, including that the alarm fires rather than only that it stays quiet:
::warning::::error::That 105% is real: 3,159 of 3,000 included minutes for August 2026, private repos only. The org is past its allowance, which is why a monetary budget was needed. (Higher than the ~2,975 I reported on the 27th — that came from a top-N list and missed three private repos.)
What is blocked
The App installation token cannot read org billing:
Billing sits behind its own App permission, "Organization plan" (read). Pick one:
app-has-billing-permission: trueon the caller. Best end state — nothing to rotate.BUDGET_TOKENsecret with org billing read access. Works immediately, no App changes. It deliberately outranks the plain App token.Until then the watchdog runs, fails loud, and prints exactly that remedy.
Two things learned the hard way, worth keeping
create-github-app-tokenfails the job when asked for a permission the installation lacks — it does not degrade. Requesting it optimistically (fix(budget): request the billing permission and say so when it is missing #130) took the whole watchdog down at token minting, producing a zero-job run that fails in seconds. That signature is indistinguishable from an exhausted Actions quota, i.e. the exact condition this watchdog exists to detect. fix(budget): make the billing permission opt-in so it degrades instead of dying #131 made the permission opt-in for that reason.Cadence caveat, unchanged
The caller is scheduled, and GitHub throttles schedules hard in this org — 4–7 runs per repo per day regardless of declared cron (JorisJonkers-dev/.github#42). Fine for a budget check; it cannot promise to warn within minutes, and the workflow comment says so.
feat(budget): price runs from the Actions API when billing is unreach…
fix(budget): refuse to report a percentage the watchdog cannot substa…
ExtraToast commentedon Aug 30, 2026
Status: red, correctly, and blocked on one grant
Two rounds of fixes since the last comment, the second undoing a defect the first introduced.
What I got wrong
#132 added an Actions API fallback so the watchdog could work without the billing permission. It made the watchdog go green reporting
0 of 3000 (0%)— it could read neither billing nor runs, warned about both, then reported a comfortable zero as a pass.That is worse than the red it replaced. It is also precisely what I had warned against in my own commit message two commits earlier: a budget watchdog that reports no usage because it cannot see usage looks like good news. The org was at 109% while it reported 0%.
#133 fixes it: a partial or empty read is now refused, not averaged into a percentage.
Current production behaviour, verified:
The blocker did not move
My attempt to remove the owner action failed — it only changed which permission is needed. The fallback needs
actions: readorg-wide, which is the same class of grant as billing. So, unchanged from before, one of:Organization plan(read) — exact figures, run takes seconds. Best end state.Actions(read) — fallback works, run takes ~4 minutes, figure is approximate.BUDGET_TOKENsecret with either access — works immediately, no App change.The capable token is now minted with
continue-on-error, so the moment any of those is done it is picked up automatically with nothing to configure. Theapp-has-billing-permissionflag is gone.Accuracy, measured rather than asserted
When the fallback can read runs, against billing for the same month:
I had documented the fallback as a lower bound until this measurement contradicted it. The error has no known sign — re-run attempts overcount, retention ageing undercounts — so it is now labelled approximate, with an explicit warning against quoting it.
One finding worth keeping regardless of this issue: the runs endpoint caps a single query at 1000 results and says nothing when it truncates.
fleet-infraalone exceeds that in a month — 1000 returned unwindowed against 1337 with weekly windows, silently losing a quarter of the month. Any future tooling that counts runs needs to window.Meanwhile
August closed over the included allowance — 3,268 of 3,000 as of this comment — so the monetary budget is doing real work. That number came from the billing API via a PAT, not from the watchdog.