Appearance
GitHub Actions usage forensics
Assessed 2026-09-23. The question: is QR Setu genuinely consuming 2,000 Actions minutes in the first days of each month, or is something wired wrongly? And, keeping the existing setup, can we run on GitHub under a hard $10/month ceiling after optimising?
ANSWER
The consumption is abnormal, and the cause is identified.
87% of the minutes billed in August and September were not our development.
| Where the minutes went (Aug + Sep) | Minutes | Share |
|---|---|---|
Scheduled nightly native builds (parity-native) | 2,275 | 57% |
| CI triggered by Dependabot PRs (qrsetu + nefoxx) | 1,142 | 29% |
| Other schedules (env-drift, watchdog, security) | 44 | 1% |
| Our own pushes and PRs | 538 | 13% |
- The nightly builds re-built an unchanged commit on a 10× macOS runner and failed every single night (24 of 24 runs).
- The 5-day burn in September was the nightly schedule: 1,329 minutes in 1–4 September, from 4 runs.
- That schedule is already gone (removed 4 Sep; the workflow is also disabled in the UI).
- The second leak, Dependabot, was 85% of all usage in the 10 days after the fix. Removed on 2026-09-23 in both
qrsetuandnefoxx(owner decision); they share the quota. - Native builds are now manual-only (2026-09-23): no schedule, no push trigger. A developer starts them and picks Android, iOS or both.
- No workflow in any of the account's repos runs on a schedule any more (2026-09-23): the weekly security scan, the payments watchdog cron and nefoxx's nightly e2e-regression were removed too. Every workflow can be started manually.
With the changes implemented on 23 Sep, QR Setu needs roughly 1,300 minutes/month at August's development pace, plus whatever manual native runs a developer asks for. That leaves ~700 of the free 2,000 for those runs; see Minutes saved. A $10 budget raises the ceiling to about 3,670 minutes/month, roughly 2.5× today's need.
Recommendation: optimise and stay on GitHub with a $10 budget. No infrastructure or architecture change is needed.
How this was measured
- Source. GitHub's REST API: every workflow run and every job in the three repos that use Actions under the
digious-platformsaccount, July to 23 Sep 2026.qrsetu: 977 runs, 1,906 jobsnefoxx: 238 runs, 475 jobsdigious: 52 runs, 92 jobs
- Billing rules applied. Each job's
started_at→completed_at, rounded up to a whole minute per job, macOS at 10×. Dependabot's own update runs are excluded, because they are free: "Running Dependabot on standard GitHub-hosted or self-hosted runners does not count towards your included GitHub Actions minutes" (GitHub Docs).
Three things that would have made the numbers wrong
- The quota is per ACCOUNT, not per repo. Six private repos share the 2,000 minutes (
qrsetu,nefoxx,digious,vinayaka-developers,fylerr,sprutt); three of them use Actions. Looking atqrsetualone misses 8% of the usage. - 855 "failed" jobs never ran. Once the quota is spent, GitHub refuses queued jobs without giving them a runner, and they bill nothing. Their annotation reads: "The job was not started because recent account payments have failed or your spending limit needs to be increased." They are excluded here; counting them at one minute each overstated August by ~1,000 minutes.
- GitHub's
/timingendpoint reports 0 billable minutes for every run on the new billing platform. It cannot be used.
Cross-check. This reconstruction reaches 1,968 minutes on 14 September. GitHub started refusing jobs at 08:23 UTC on 15 September. That is within ~1.6% of the 2,000 limit, so the reconstruction matches GitHub's own count closely.
- The authoritative figure needs the billing usage report, which requires a token scope we don't have (
gh auth refresh -h github.com -s user), or Settings → Billing → Usage. - The account is quota-blocked right now. Every job submitted since 15 Sep 08:23 UTC has failed without running, so those red checks are not test failures.
1 · What consumed the 2,000 minutes
| August | September (1–14, then blocked) | |
|---|---|---|
| Minutes billed (reconstructed) | 2,031 (quota crossed 11 Aug) | 1,968 (blocked from 15 Sep) |
| …on scheduled runs | 959 (47%) | 1,360 (69%) |
| …on Dependabot-PR CI | 585 (29%) | 557 (28%) |
| …on our own pushes and PRs | 487 (24%) | 51 (3%) |
| Share of billed minutes on jobs that failed | 63% | 90% |
| By repo | qrsetu 95% · nefoxx 5% · digious 1% | qrsetu 88% · nefoxx 12% |
Daily burn, 1–4 September: 318 → 358 → 441 → 386 minutes. That's 1,503 minutes in four days. Almost all of it was the nightly parity-native schedule plus Monday's Dependabot batch.
The biggest single jobs (Aug + Sep, executed runs only):
| Job | Runs | Avg min | Minutes | Failed | Verdict |
|---|---|---|---|---|---|
parity-native · iOS (macOS, 10×) | 12 | 14.8 | 1,840 | 12 of 12 | nightly re-build of an unchanged commit; failed every time at "Build for the simulator" |
parity-native · Android | 22 | 31.0 | 693 | 19 of 22 | failed at "Boot emulator, install, assert coherence" |
ci · e2e-web | 37 | 12.6 | 486 | 16 | runs even when workspace has already failed |
ci · workspace (lint, type-check, tests) | 39 | 5.0 | 216 | 19 | Dependabot PRs fail type-check here |
nefoxx CI · Lint, Test, Build | 24 | 8.0 | 202 | 0 | healthy |
nefoxx CI · SonarQube Scan | 24 | 4.6 | 120 | 24 of 24 | a gate that is always red |
security · semgrep + gitleaks | 44 each | 1.5 / 0.4 | 144 | 22 | fine |
ci · sonar | 18 | 3.9 | 78 | 9 | fine |
env-drift · drift (daily cron) | 25 | 0.2 | 25 | 21 of 25 | fails daily comparing Dev with an empty Prod |
2 · What is unnecessary or inefficient, with the evidence
| # | Finding | Evidence | Minutes (Aug + Sep) | Status |
|---|---|---|---|---|
| 1 | Nightly native builds re-tested unchanged code, on macOS, and always failed | 24 scheduled runs, 24 failed; 39 scheduled runs re-tested a commit the same workflow had already tested (2,066 of 2,319 scheduled minutes) | 2,275 | ✅ fixed: schedule removed 4 Sep; manual workflow_dispatch only for both platforms since 23 Sep (platform input, push trigger and gate job removed) |
| 2 | Every Dependabot PR runs the full CI, including e2e, sonar and card-ui-gate, in two repos | 80 Dependabot PR branches → 139 CI runs; weekly across 3 ecosystems in both qrsetu and nefoxx; each rebase re-triggers | 1,142 | ✅ qrsetu fixed 23 Sep: every PR-triggered job skips Dependabot PRs; a bump is tested once on the develop push after merge · ✅ nefoxx fixed 23 Sep (main + develop) |
| 3 | No fail-fast in ci.yml: e2e-web, card-ui-gate and docs run in parallel with workspace instead of after it | 25 runs where workspace failed and the other jobs still spent 323 minutes | 323 (all periods) | ✅ fixed 23 Sep: needs: workspace (nefoxx already had it) |
| 4 | Gates that are always red still spend minutes: nefoxx Sonar (44 of 45 failed), env-drift (21 of 25), card-ui-gate on Dependabot PRs (12 of 12) | failure counts above | ≈ 170 | ✅ mostly gone: nefoxx Sonar and card-ui-gate no longer run on Dependabot PRs; env-drift has no daily cron |
| 5 | The Android job fails 86% of the time (emulator step), at 31 minutes a run | 19 of 22 | 693 | ⏸ workflow disabled; this is a broken test, not CI wiring |
| 6 | parity-native's gate job holds a paid runner while it polls ci.yml every 20 s | avg 5.1 min per run | 23 | ✅ gate job removed 23 Sep (manual runs do not need it) |
| 7 | Per-job rounding (each job billed at ≥ 1 minute) | billed 1,446 vs actual 1,297 in Aug: ~10% | ≈ 150 | acceptable, not a root cause |
Ruled out, with evidence:
- Re-runs: 1 in two months.
- Push + PR double-triggering: owner PRs existed in July/August but are no longer used.
- Self-triggering or chained workflows: there is no
workflow_runtrigger in any repo; nefoxx'sdeploy.ymlis a reusableworkflow_call. - Missing concurrency control: every CI workflow cancels superseded runs.
- Artifact or cache storage: 0 MB in all three repos.
- Copilot code review: no Copilot-triggered runs.
3 · Optimisations that keep the existing setup
All of these are configuration edits to existing files. None changes what is tested on our own pushes.
| Change | File | Expected saving |
|---|---|---|
Dependabot weekly → monthly, in qrsetu and nefoxx | .github/dependabot.yml (both repos) | ≈ −75% of Dependabot CI |
No CI on Dependabot PRs at all (owner's choice over a "light path"): if: github.event.pull_request.user.login != 'dependabot[bot]' on every job in ci, security, backend-ci, release-gate. ✅ done in qrsetu 23 Sep; nefoxx still to do | the four workflows; nefoxx ci.yml | all Dependabot-PR CI (1,142 in Aug + Sep); each bump then costs one normal develop push run after merge |
Fail-fast: needs: workspace on e2e-web, card-ui-gate and docs (sonar already has it) | ci.yml | the 323-minute class, on every failing push. ✅ done 23 Sep (qrsetu; nefoxx already had it) |
timeout-minutes on the 16 jobs that have none (default is 360 minutes) | all workflows | prevents a hung job burning 6 hours; no saving until one hangs. ✅ done 23 Sep: all qrsetu jobs, and the 2 nefoxx jobs that lacked one |
| env-drift: daily cron removed, push + manual only (it had failed 21 of 25 runs) | env-drift.yml | ≈ −30/month. ✅ done 23 Sep |
| Fix nefoxx's Sonar baseline gate, or stop running it until it can pass | nefoxx ci.yml | ≈ −60/month |
Keep native builds manual (workflow_dispatch) until the emulator and simulator steps pass; never on a schedule | parity-native.yml (✅ done 23 Sep: dispatch-only, platform input) | holds the 2,275-minute saving, and the ~38 min per mobile push |
Combined: the Dependabot line drops from ~860/month to about 100/month. The other items remove most of the failing-gate spend.
4 · Reasonable monthly usage after optimisation
Measured cost of one of our own pushes to develop: 29 minutes. That's six pushes on 1–10 August, before the block, covering ci + security + backend-ci + release-gate. A push that also triggers the Android job adds about 38 minutes (31 for the job, plus the gate).
| Scenario | Dependabot | Schedules | Development | Manual native runs | Total / month |
|---|---|---|---|---|---|
| Setup before 23 Sep (native already disabled in the UI), 44 pushes | ≈ 860 | ≈ 43 | 44 × 29 = 1,276 | — | ≈ 2,180: over quota late in the month |
| Today's setup (implemented 23 Sep), 44 pushes | ≈ 30 | 0 | 1,276 | 0 | ≈ 1,300: ~700 spare |
| Today, 44 pushes + 8 Android + 2 iOS manual runs | ≈ 30 | 0 | 1,276 | 8 × 38 + 2 × 150 = 604 | ≈ 1,910: inside the free 2,000 |
| Today, 60 pushes + 8 Android + 2 iOS manual runs | ≈ 30 | 0 | 1,740 | 604 | ≈ 2,370: ≈ $2.24 over, well under $10 |
"Dependabot ≈ 30" is the merged bumps now tested once by an ordinary push instead of on every PR and rebase. An iOS run is ≈ 15 macOS minutes, which draws ≈ 150 at 10×.
"44 pushes" is August's measured count of distinct pushed commits to develop. September's pushes were deliberately batched, so it is not representative.
Minutes saved: previous setup vs today
Actually billed. Of the 3,999 minutes billed in August and September, 3,461 (87%) were categories that no longer run. That's ≈ 1,730 minutes a month of billed waste eliminated. Both months hit the cap, so this understates what those items would have consumed without it.
Per month, previous setup versus today:
| Item | Previous setup | Today | Saved / month |
|---|---|---|---|
| Nightly Android + iOS builds (120–330 min a night, measured Aug / Sep) | 3,600–10,000 of demand | 0 | 3,600–10,000 |
| Android build on every mobile push (the 4 Sep cadence, when enabled; ~20 pushes × 38) | ≈ 760 | 0 unless run manually | ≈ 760 |
| CI on Dependabot PRs, qrsetu + nefoxx (≈ 200 per weekly batch, measured) | ≈ 860 | ≈ 30 | ≈ 830 |
| Schedules: env-drift daily, security weekly, watchdog 14/day (when enabled) | ≈ 43, +434 with the watchdog | 0 | ≈ 43–477 |
nefoxx nightly e2e-regression (would start on the next merge to main, up to 75 min) | up to ≈ 2,250 | 0 | avoided |
Jobs that ran after workspace had already failed | ≈ 50–150 | 0 | ≈ 50–150 |
| A hung job | up to 360 per incident | capped at the job's limit (5–60) | insurance |
| Our own development (44 pushes × 29) | ≈ 1,280 | ≈ 1,280 | 0, the real work |
| Total | ≈ 5,800–12,700 of demand: quota gone in 4–11 days | ≈ 1,300 + chosen manual runs | ≈ 2,000–3,000 against the free tier in practice |
What the ~700 spare minutes buy, on request: ≈ 18 Android runs (38 each) or ≈ 4 iOS runs (≈ 150 each).
Caveats:
- The figures assume August's pace. At 60 pushes a month, development rises to ≈ 1,750.
- The watchdog and the per-push Android build were already switched off in the UI, so removing them from the files keeps them off rather than saving anything new today.
- Verification is owed: after the next monthly reset, re-run this analysis on real runs, or read the billing report (
gh auth refresh -h github.com -s user).
5 · What a $10/month ceiling buys
Pricing (runner pricing): Linux 2-core $0.006/min, macOS $0.062/min, Windows $0.010/min.
- Capacity. Beyond the 2,000 included minutes, $10 buys ≈ 1,666 Linux minutes, for a total of ≈ 3,666 minutes/month.
| What fits in ~3,666 min/month (after ~118 fixed) | Count |
|---|---|
| Plain pushes (29 min: web / backend / packages without Android) | ≈ 120 / month |
| Mobile pushes with the Android job (≈ 67 min) | ≈ 53 / month |
| iOS manual runs alone (≈ 15 macOS min ≈ $0.93 each beyond the included minutes) | ≈ 10 on the $10 alone |
6 · Paid usage and cost per additional run
| Option | Included | Extra capacity | Cost |
|---|---|---|---|
| Free + payment method + $10 Actions budget (recommended) | 2,000 | +1,666 Linux min | ≤ $10 |
| Pro (personal account) + $6 budget | 3,000 | +1,000 Linux min | Pro price UNVERIFIED here (historically $4/month); ≈ 4,000 total, only marginally more than the Free option |
Marginal cost per run beyond the included minutes:
| Run type | Cost |
|---|---|
| Plain push | ≈ $0.17 |
| Push with Android | ≈ $0.40 |
| Android-only run | ≈ $0.23 |
| iOS run | ≈ $0.93 |
Tax
Budgets are set on pre-tax amounts. Whether GST is added to GitHub invoices for this account is UNVERIFIED; check the next invoice.
7 · How long $10 lasts
It's a monthly ceiling, and at our current stage it doesn't bind. Optimised usage is ~1,400–2,300 minutes/month, so the likely spend is $0–2/month.
The $10 ceiling starts to bite at about 3,666 minutes/month:
- about 120 plain pushes, or
- about 53 mobile pushes with Android,
which is roughly 2.5–3× August's pace.
Revisit when a second developer joins, native CI goes back on every push, or the Dependabot light path is removed.
8 · Architecture or configuration?
Configuration only. No runner change, no new infrastructure, no repository restructuring. The edits are:
dependabot.ymlin two repos;- a handful of
if:,needs:andtimeout-minutes:lines; - one cron change;
- the nefoxx Sonar gate.
The one item that is real engineering is making the native tests pass. The Android emulator step fails 86% of the time and the iOS simulator build 100%, so those jobs cost minutes and prove nothing. Until they pass, they stay manual.
9 · Safeguards against another quota burn
- A hard cap on the GitHub side. Add a payment method and an Actions budget of $10 with "Stop usage when budget limit is reached", plus alerts (budgets).
- Unlike AWS, GitHub can stop usage at the limit. The docs warn it can be exceeded in the first cycle after creation.
- Timeouts on every job. 16 jobs currently default to 360 minutes.
- No
schedule:on macOS or heavy jobs. A schedule that re-runs an unchanged commit is paying to repeat a known answer; 89% of scheduled minutes were exactly that. - One Dependabot policy for every repo that shares the quota (monthly + light CI).
nefoxxis part of QR Setu's budget whether or not it is part of QR Setu. - Fail-fast ordering. Cheap gates first, expensive jobs
needs:them. - A circuit breaker for always-red gates. A job that has failed 10 runs in a row gets fixed or switched to manual. env-drift and nefoxx Sonar have been red for weeks, spending minutes to report a known state.
- A usage report, proposed and not built. A
check:actions-usagescript that reads the billing usage API weekly (needs theuserscope once) and prints month-to-date minutes per repo and workflow, plus the projected month-end against the budget.- The scripts used for this assessment already compute exactly that from run history.
- It would have flagged the nightly iOS burn on day one instead of day four.
- What it cannot see: whether a run was useful. That stays a judgment call.
Recommendation
Stay on GitHub. The configuration was the cause, and it is now fixed. Add a payment method and a $10 hard budget only if usage ever needs it; that is deferred by owner choice.
- Measured need after the changes: ≈ 1,300 minutes/month at August's pace, plus manual native runs on request, against the free 2,000 (or ~3,670 with a $10 budget).
- The cause: the 5-day burn came from one schedule and from Dependabot CI, not from QR Setu's real workload.
- Why not move runners: moving them elsewhere (see AWS compute for Actions runners) would treat a symptom whose cause was configuration.
Status of the work (2026-09-23):
| Item | Status |
|---|---|
| Native builds manual-only, with a platform choice | ✅ done |
| No CI on Dependabot PRs, qrsetu + nefoxx | ✅ done |
| No scheduled runs in any repo; every workflow dispatchable | ✅ done |
Fail-fast needs: workspace in ci.yml | ✅ done (nefoxx already had it) |
timeout-minutes on every job | ✅ done |
Enable parity-native in the UI before its first manual run | ⏳ owner, once |
| $10 budget with "stop usage" | ⏸ deferred by owner |
| Native jobs that never pass (QRS-1286), always-red env-drift (QRS-1287) | 🔴 open |
| Confirm the projection on real runs after the next monthly reset | ⏳ owed |
Sources
- GitHub REST API run and job history for
digious-platforms/{qrsetu,nefoxx,digious}, fetched 2026-09-23. The analysis scripts are reproducible from the session scratchpad and can becomecheck:actions-usage. - Dependabot on Actions runners: update runs don't count toward included minutes.
- Actions runner pricing · included usage (Free 2,000, Pro 3,000) · GitHub's plans
- Actions billing: blocked when quota is used without a payment method · budgets and "stop usage"
Unverified:
- the authoritative billed total (the reconstruction is within ~1.6%);
- the Pro price;
- GST on GitHub invoices;
- how macOS draws down included minutes after the 1 Jan 2026 price change (10× assumed).