improvement(ci): scan CodeQL PRs at the promotion boundary, refresh main daily
Summary
CodeQL runs ~3,090 times a month and costs ~$382/mo across both languages. 90 of the last 100 PRs target staging, and every one of those is scanned again — against the exact tree being promoted — when the staging→main PR opens.
- PR scans restricted to
main. This defers the signal to the promotion boundary rather than dropping it. Verified safe: no ruleset or branch protection requires a CodeQL status check (all four rulesets carry onlypull_request/deletion/non_fast_forward), and the code-scanning alert view is fed by the push-to-mainand scheduled analyses — PR analyses upload againstrefs/pull/N/mergeand only ever surface as PR annotations. - Deliberately a branch cut, not an activity-type cut. Dropping
synchronizewould have cut a similar share, but it scans a PR's first commit and never its final state — backwards, since review fixups land in later pushes. - Scheduled scan weekly → daily. Pushes to
mainare rare, so with PR scans limited tomainthe alert view leans on the cron more than before. It also reseeds the overlay-base database (see below). Costs a few dollars a month. - Documented a trap in
codeql-config.yml: addingqueries:/packs:/query-filters:tripsOverlayDisabledReason.NonDefaultQueriesand permanently disables overlay analysis — trading a documented up-to-10x win for a few percent.
Follow-up, not fixed here
The scan is slow for a reason unrelated to scope: the overlay-base database uploads on every push to main and never restores on a PR run. Observed 4/4 misses against a byte-identical cache key:
Looking in Actions cache for overlay-base database with restore key
codeql-overlay-base-database-1-<hash>-javascript-<version>-
No overlay-base database found in Actions cache
...49 minutes after a run logged Successfully uploaded overlay-base database with a key matching that prefix exactly. Phase timings show extraction is 118s of a 221s run (53%) — precisely what overlay skips. GitHub's cache API holds zero overlay entries, so the multi-GB base is going into the runner provider's transparent cache rather than GitHub's. Fixing that restore is worth roughly half of every PR scan and is tracked separately.
Type of Change
- Improvement
Testing
actionlint clean. Verified the parsed triggers (pull_request.branches: [main], unchanged types, push: [main], daily cron) and that both matrix languages still expand. bun run lint and all 38 check:audits pass.
Checklist
- Code follows project style guidelines
- Self-reviewed my changes
- Tests added/updated and passing
- No new warnings introduced
- I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)