Skip to content

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)MinutesShare
Scheduled nightly native builds (parity-native)2,27557%
CI triggered by Dependabot PRs (qrsetu + nefoxx)1,14229%
Other schedules (env-drift, watchdog, security)441%
Our own pushes and PRs53813%
  • 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 qrsetu and nefoxx (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-platforms account, July to 23 Sep 2026.
    • qrsetu: 977 runs, 1,906 jobs
    • nefoxx: 238 runs, 475 jobs
    • digious: 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

  1. 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 at qrsetu alone misses 8% of the usage.
  2. 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.
  3. GitHub's /timing endpoint 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 ​

AugustSeptember (1–14, then blocked)
Minutes billed (reconstructed)2,031 (quota crossed 11 Aug)1,968 (blocked from 15 Sep)
…on scheduled runs959 (47%)1,360 (69%)
…on Dependabot-PR CI585 (29%)557 (28%)
…on our own pushes and PRs487 (24%)51 (3%)
Share of billed minutes on jobs that failed63%90%
By repoqrsetu 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):

JobRunsAvg minMinutesFailedVerdict
parity-native · iOS (macOS, 10×)1214.81,84012 of 12nightly re-build of an unchanged commit; failed every time at "Build for the simulator"
parity-native · Android2231.069319 of 22failed at "Boot emulator, install, assert coherence"
ci · e2e-web3712.648616runs even when workspace has already failed
ci · workspace (lint, type-check, tests)395.021619Dependabot PRs fail type-check here
nefoxx CI · Lint, Test, Build248.02020healthy
nefoxx CI · SonarQube Scan244.612024 of 24a gate that is always red
security · semgrep + gitleaks44 each1.5 / 0.414422fine
ci · sonar183.9789fine
env-drift · drift (daily cron)250.22521 of 25fails daily comparing Dev with an empty Prod

2 · What is unnecessary or inefficient, with the evidence ​

#FindingEvidenceMinutes (Aug + Sep)Status
1Nightly native builds re-tested unchanged code, on macOS, and always failed24 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)
2Every Dependabot PR runs the full CI, including e2e, sonar and card-ui-gate, in two repos80 Dependabot PR branches → 139 CI runs; weekly across 3 ecosystems in both qrsetu and nefoxx; each rebase re-triggers1,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)
3No fail-fast in ci.yml: e2e-web, card-ui-gate and docs run in parallel with workspace instead of after it25 runs where workspace failed and the other jobs still spent 323 minutes323 (all periods)✅ fixed 23 Sep: needs: workspace (nefoxx already had it)
4Gates 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
5The Android job fails 86% of the time (emulator step), at 31 minutes a run19 of 22693⏸ workflow disabled; this is a broken test, not CI wiring
6parity-native's gate job holds a paid runner while it polls ci.yml every 20 savg 5.1 min per run23✅ gate job removed 23 Sep (manual runs do not need it)
7Per-job rounding (each job billed at ≥ 1 minute)billed 1,446 vs actual 1,297 in Aug: ~10%≈ 150acceptable, 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_run trigger in any repo; nefoxx's deploy.yml is a reusable workflow_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.

ChangeFileExpected 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 dothe four workflows; nefoxx ci.ymlall 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.ymlthe 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 workflowsprevents 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 passnefoxx ci.yml≈ −60/month
Keep native builds manual (workflow_dispatch) until the emulator and simulator steps pass; never on a scheduleparity-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).

ScenarioDependabotSchedulesDevelopmentManual native runsTotal / month
Setup before 23 Sep (native already disabled in the UI), 44 pushes≈ 860≈ 4344 × 29 = 1,276—≈ 2,180: over quota late in the month
Today's setup (implemented 23 Sep), 44 pushes≈ 3001,2760≈ 1,300: ~700 spare
Today, 44 pushes + 8 Android + 2 iOS manual runs≈ 3001,2768 × 38 + 2 × 150 = 604≈ 1,910: inside the free 2,000
Today, 60 pushes + 8 Android + 2 iOS manual runs≈ 3001,740604≈ 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:

ItemPrevious setupTodaySaved / month
Nightly Android + iOS builds (120–330 min a night, measured Aug / Sep)3,600–10,000 of demand03,600–10,000
Android build on every mobile push (the 4 Sep cadence, when enabled; ~20 pushes × 38)≈ 7600 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 watchdog0≈ 43–477
nefoxx nightly e2e-regression (would start on the next merge to main, up to 75 min)up to ≈ 2,2500avoided
Jobs that ran after workspace had already failed≈ 50–1500≈ 50–150
A hung jobup to 360 per incidentcapped at the job's limit (5–60)insurance
Our own development (44 pushes × 29)≈ 1,280≈ 1,2800, 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 ​

OptionIncludedExtra capacityCost
Free + payment method + $10 Actions budget (recommended)2,000+1,666 Linux min≤ $10
Pro (personal account) + $6 budget3,000+1,000 Linux minPro 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 typeCost
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.yml in two repos;
  • a handful of if:, needs: and timeout-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 ​

  1. 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.
  2. Timeouts on every job. 16 jobs currently default to 360 minutes.
  3. 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.
  4. One Dependabot policy for every repo that shares the quota (monthly + light CI). nefoxx is part of QR Setu's budget whether or not it is part of QR Setu.
  5. Fail-fast ordering. Cheap gates first, expensive jobs needs: them.
  6. 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.
  7. A usage report, proposed and not built. A check:actions-usage script that reads the billing usage API weekly (needs the user scope 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):

ItemStatus
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 ​

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).