Skip to content

GitHub Actions minute usage β€” analysis (#832)

Generated 2026-05-28. Estimates derived from the cron cadences in .github/workflows/ and GitHub’s per-job billing model. We do not have the Actions billing/usage API wired up, so these are modeled figures, not billed actuals β€” treat the ranking as reliable and the absolute minutes as order-of-magnitude.

Billing model (why this matters)

GitHub Actions bills per job, and each job’s wall-clock time is rounded up to the next whole minute. A job that does 20 seconds of real work still costs 1 minute. Consequence: high-frequency, short jobs are the most wasteful thing you can run β€” you pay the 1-minute floor on every single invocation, no matter how trivial the work.

Standard private-repo allowances: Free 2,000 min/mo, Pro 3,000, Team 3,000. The scheduled workflows below alone exceed all of these, which is the structural reason for the overage.

Workflow inventory

WorkflowTriggerCadenceRuns/dayRunnerNotes
cost-trickle-monitor.ymlschedule2,12,22,32,42,52 * * * *144ubuntucold npm install each run; no cache
cost-spike-monitor.ymlschedule15 * * * *24ubuntucold npm install; no cache
vendor-balance-fetcher.ymlschedule25 * * * *24ubuntucold npm install; no cache
smoke.ymlschedule5 * * * *24ubuntunpm ci on full monorepo each run
rotation-reminder.ymlschedulequarterly~0.03ubuntunegligible
deploy.ymlpush main (apps/**,packages/**,some workers) + dispatchper pushvariableubuntuup to 15 jobs defined
deploy-aws.ymlpush main (workers paths) + dispatchper pushvariableubuntu2 jobs
audit.ymlPR + after every Deploy on mainper PR + per deployvariableubuntumatrix Γ—2 (marketing-site, pdf-converter)
test.ymlPRper PRvariableubuntuβ€”
rotate-secrets.ymldispatch onlymanual~0ubuntunot scheduled

Estimated scheduled-minute consumption

Assuming a realistic ~2 min billed per monitor run (VM provision + checkout + setup-node + cold install + script) and ~5 min for the smoke (npm ci on the whole monorepo):

WorkflowRuns/moEst. min/runEst. min/moShare
cost-trickle-monitor4,32028,640~57%
smoke72053,600~24%
cost-spike-monitor72021,440~10%
vendor-balance-fetcher72021,440~10%
rotation-reminder~11~1~0%
Scheduled total~15,100

Even at the absolute floor (1 min/run for monitors, 3 min for smoke) the scheduled total is ~7,900 min/mo β€” still 2.5–4Γ— any standard allowance. Event-driven workflows (deploy, audit, test) add a variable amount on top, scaling with commit/PR volume.

cost-trickle-monitor alone is the single largest line item (~57% of scheduled minutes) purely because it runs 144Γ—/day and pays the per-job minute floor 144 times for a few seconds of real work each time.

Structural causes (ranked)

  1. Per-job minute floor Γ— high frequency. The trickle monitor’s 6Γ—/hour cadence is the dominant cost. This is intrinsic to running sub-minute work on Actions; no amount of in-job optimization removes the floor.
  2. Heavy install on the smoke. npm ci installs the entire monorepo every hour to run a small e2e smoke.
  3. No dependency caching on the 3 monitors. Each cold-installs @supabase/supabase-js. Real but secondary β€” caching saves seconds, the floor costs the whole minute.
  4. audit.yml runs after every Deploy (matrix Γ—2) on top of PR runs, coupling its cost to deploy frequency and always running both targets.
  5. deploy.yml fan-out β€” up to 15 ubuntu jobs; if path filters/per-app conditionals don’t tightly gate them, unrelated pushes fan out.

Recommendations (prioritized by savings Γ· risk)

P0 β€” Move the 3 high-frequency monitors off Actions Β· ~11,500 min/mo

cost-trickle-monitor, cost-spike-monitor, and vendor-balance-fetcher are plain .mjs scripts hitting Supabase + provider APIs. They belong on a cron platform that runs sub-minute jobs at ~zero marginal cost with no per-minute floor:

  • Supabase pg_cron + pg_net β€” already used here (app_logs_retention, email-notify-sweep). Best fit for the Supabase-writing monitors.
  • Cloudflare Worker Cron Triggers β€” the platform already deploys Workers; good fit if the script needs npm deps / longer runtime than pg_net allows.

Same cadence, same coverage β€” only the execution location changes. Caveat: requires relocating the relevant secrets to the new platform, so this needs sign-off and a careful secrets move.

P1 β€” If monitors stay on Actions

  • Halve trickle cadence (2,22,42 = 3Γ—/hr) β†’ ~4,300 min/mo saved. Changes detection latency (~5–10 min β†’ ~10–20 min) β€” needs sign-off.
  • Add dependency caching to the 3 monitors. Low risk, marginal (~seconds/run).

P2 β€” Smoke Β· ~1,800–2,400 min/mo

  • Install only the @accessible-pdf/e2e-smoke workspace instead of npm ci on the full monorepo, or cache aggressively.
  • Consider every 2–3h instead of hourly, or move it to a Cloudflare Cron + alert like the monitors. Cadence change needs sign-off.

P3 β€” audit / deploy

  • After a Deploy, run only the audit matrix target relevant to what shipped (not always both marketing-site + pdf-converter).
  • Verify deploy.yml path filters + per-app conditionals actually gate the 15 jobs so unrelated changes don’t fan out.

Bottom line

The overage is driven by scheduled monitors paying the per-job minute floor at high frequency, not by CI on PRs. Moving the three monitors to Supabase pg_cron / Cloudflare Cron (P0) removes ~75% of scheduled minutes by itself and is the highest-leverage change; trimming the smoke install/cadence (P2) is the next-largest. The remaining items are smaller polish.