Skip to content

Warn before the Actions minute budget runs out #126

Closed

Description

@ExtraToast

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

  1. added
    type: featureNew user-facing or operator-facing capability.
    area: toolingReusable workflows, Gradle, templates, Renovate, and API tooling.
    component: ciContinuous integration workflow or check behavior.
    priority: P1High; important and should be handled in the current iteration.
    on Aug 27, 2026
  2. ExtraToast commented on Aug 29, 2026

    @ExtraToast
    ContributorAuthor

    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:

    allowance usage branch exit
    10000 32% notice only 0
    4200 75% ::warning:: 0
    3000 (real) 105% ::error:: 1

    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:

    ##[notice]10 private repo(s) of 33 in JorisJonkers-dev        <- token fine here
    ##[warning]gh api .../settings/billing/usage failed: 403 Resource not accessible by integration
    

    Billing sits behind its own App permission, "Organization plan" (read). Pick one:

    1. Grant the App that permission, accept the updated installation permissions, then set app-has-billing-permission: true on the caller. Best end state — nothing to rotate.
    2. Add a BUDGET_TOKEN secret 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

    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.

  3. ExtraToast commented on Aug 30, 2026

    @ExtraToast
    ContributorAuthor

    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.

    • any rejected run-listing query → fail, naming the grant needed
    • zero runs across every private repo in a month → fail; that is not a small number, it is evidence the measurement did not happen

    Current production behaviour, verified:

    ##[notice]10 private repo(s) of 34 in JorisJonkers-dev
    ##[error]could not list workflow runs (50 query/queries refused), so the month cannot be priced.
    ##[error]This needs actions: read across the org. Grant the App "Actions" (read) and
             "Organization plan" (read), accept the updated installation permissions,
             or supply BUDGET_TOKEN.
    

    The blocker did not move

    My attempt to remove the owner action failed — it only changed which permission is needed. The fallback needs actions: read org-wide, which is the same class of grant as billing. So, unchanged from before, one of:

    1. Grant the App Organization plan (read) — exact figures, run takes seconds. Best end state.
    2. Grant the App Actions (read) — fallback works, run takes ~4 minutes, figure is approximate.
    3. Add a BUDGET_TOKEN secret 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. The app-has-billing-permission flag is gone.

    Accuracy, measured rather than asserted

    When the fallback can read runs, against billing for the same month:

    source figure
    billing API 3263
    fallback, weekly-windowed 3386 (+4%)
    fallback, letting the runs endpoint truncate 3008 (−5%)

    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-infra alone 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: toolingReusable workflows, Gradle, templates, Renovate, and API tooling.component: ciContinuous integration workflow or check behavior.priority: P1High; important and should be handled in the current iteration.type: featureNew user-facing or operator-facing capability.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions