Investigation Β· CI & test strategy

Are the tests worth it, and where should each gate live?

For Gera to poke holes in Author: Agent Smith 2026-07-24

You sent a voice memo asking six things about our test/CI/deploy machinery. This is the investigation you asked for β€” your questions as sections, answered with measured data where it exists, marked as opinion where it doesn't. Every number is tagged: 🟒 measured (run ID / API), 🟑 estimate (source named), πŸ”΄ opinion.

Your memo, verbatim (transcribed via Whisper, 2026-07-24). The paraphrase I posted in Slack dropped nuance, so here is the source so you can correct any reading below:
"I'm just going to ramble. I hope you transcribe this, but I'm just curious β€” how valuable are these tests, because this has been a constant problem this week where tests are blocking and merges are blocking developer progress. We want high throughput, smooth systems… design, plan, implement, open PR, hopefully PR reviewed and merged as fast as possible β€” and a huge bottleneck has been the PR reviewer, but that's a pretty necessary system. Then the other is making sure we're not merging any issues. So we do need tests, but the question becomes where do we do all this? … Maybe merge to main but deploy in batches β€” every hour or two, and those batches are where the tests run… and we just always work on preview. … how do we reduce this gap? … the GitHub reviewer β€” do we need multi-turn or is one turn enough? … we have over 30,000 tests, so I don't want to create so many that it balloons and bloats our quality and friction. Should we give expiration dates to certain tests… remove tests once they expire… slim down tests, or test per impact? So if we change file B, only test file B and maybe adjacent siblings. Analyze the current infrastructure and propose a preview or artifact… use pretty HTML so I can follow along."

TL;DR β€” the six answers in one screen

Your questionShort answerConfidence
1. Are the tests worth it?Yes β€” but not equally. Round-1 review + the affected-subset suite earn their keep; the full suite on every event does not.🟒/πŸ”΄
2. Where should each gate live?Already fixed tonight: cheap affected tests gate PR + main; the full suite is nightly + hub-escalation. Batching is a downgrade from what we now have.🟒
3. Is the reviewer earning its cost?Round 1 yes (67% of PRs change after it). Round 2+ is a coin flip β€” make it opt-in.🟑
4. Test expiration / lifecycleDon't expire by date. Expire by coverage + failure signal. Concrete mechanism below.πŸ”΄
5. Test-per-impact (file B β†’ test B + siblings)~60% shipped tonight (the affected-graph model). The remaining 40% is sibling-adjacency + the deletion-safety story.🟒
6. A pretty HTML doc to follow alongYou're reading it.🟒

Methodology & data sources

Every number below traces to one of: the GitHub billing API (real spend export), the Actions runs API (per-job billed minutes, ceil to the 1-min floor GitHub bills at), or a replay (real merged trees re-run through the new selection logic). The measured dataset β€” 145 push-run timings Jul 22–23, step-level before/afters, the 30-push replay split, per-actor claude.yml attribution, and the review-family run counts β€” was gathered during the 2026-07-23 cost sprint.

🟒 measured = a run ID or the billing API backs it Β· 🟑 estimate = derived, source named Β· πŸ”΄ opinion = a recommendation, not a measurement.

1 Β· Are the tests worth it?

The honest answer is that "the tests" is the wrong unit β€” the question is which tests, run how often. We have 🟑 ~30k tests (your number; the suite runs 12-way sharded). The cost was never the tests existing β€” it was running all of them on every merge.

🟒 Before tonight, the full 12-shard suite ran on every push:main β€” 46 full runs on Jul 23, 51 on Jul 22 β€” at 57–59 billed job-min each (runs API, median 59, IDs 30052721572…30060253715). That's ~2,700 job-min/day on the test lane alone, the single biggest line item behind July running 172% of the $200 budget.

πŸ”΄ So: tests are worth it as a merge gate (they stop real regressions β€” see Β§3, where the bot + suite caught a prod-freezing deploy bug tonight). They are not worth running exhaustively on every event. The fix isn't fewer tests β€” it's running the relevant ones per event and the full set on a cadence. Which is exactly what shipped tonight.

2 Β· Where should each gate live? (and the batched-deploy question)

🟒 As of tonight the gates are placed like this:

EventWhat runsWhy
PR pushAffected-subset tests (capped) + review round 1Fast feedback; catches the author's own diff
Merge to mainAffected-subset again (its own diff) β†’ deploy gateEvery merge tested exactly for what it changed
Hub-sized merge (>200 files)Escalates to the full 12-shard suiteWide blast radius still gets full coverage before deploy
NightlyFull 12-shard suiteCross-file backstop for anything the affected graph misses

🟒 Result, measured on the first real runs: push:main billed cost 59 β†’ 10 job-min (βˆ’83%), mergeβ†’prod promote 4.8 β†’ 3.7 min, and the reviewer stays where it's cheap (the branch).

On "deploy in batches (hourly/2h/6h)"

πŸ”΄ I'd argue against it, and here's the trade with tonight's numbers as the baseline. Batching means: merge freely, hold deploys, run heavy tests at the batch boundary, ship on a timer. The case for it is that you run the expensive suite fewer times. But we just achieved that without batching β€” the affected-subset model already cut the per-merge cost 83%, and the full suite already only runs nightly + on hub-merges. Batching would buy little additional cost saving.

🟒 What batching would cost: latency. Tonight mergeβ†’prod is 3.7 min continuous. An hourly batch reintroduces up to 60 min of deploy staleness by construction; a 6h batch, up to 6h. It also re-creates the "which of the N merged PRs broke the batch?" untangling problem that the merge queue had (that pain is exactly why ADR-0107 retired the queue).

The one place batching genuinely wins is if you want a human release gate β€” someone eyeballs a batch before it ships. That's a real product/safety choice, not a cost lever. It's written up as "Option 3" in the deploy-model options doc β€” if that's what the memo was reaching for, decide it there, on its own merits, not as a spend fix.

On "always work on preview / preview mirrors raw data with PII protection"

πŸ”΄ We're close but not there. Previews are opt-in today (a [preview] commit builds one). "Always on preview with a real-time data mirror" is a bigger build β€” it needs the anonymized-subset pipeline (ADR-0097 already rewrites PII in-flight into a fake +1206/@example.test convention for stage). Wiring that into a per-branch live preview is a genuine project, not a config flip. Worth scoping separately if you want it; flagging honestly that it's not a tonight-sized change.

3 Β· Is the GitHub reviewer earning its cost? (multi-turn vs one turn)

🟑 Proxy method, stated as such: "a commit landed after review round N" can include unrelated pushes, so these are upper bounds on review-driven change. Sample: last 57 merged PRs with bot reviews, ex-Dependabot.

SignalValueRead
PRs that changed after review round 167%Round 1 clearly earns its keep
PRs that go multi-round (2+)44%Nearly half
…of those, any code change after round 248%A coin flip
Median review rounds / max1 / 7One PR ate seven rounds
Cost per extra round~5.6 minWall-clock, blocks merge

🟒 Round 1 is not the thing to cut β€” tonight alone the bot caught a deploy-ordering race that would have frozen every prod promote, plus real bugs in the cost-sprint scripts. That's the reviewer paying for itself in a single night.

Recommendation (πŸ”΄ opinion, data-informed): keep round 1 mandatory; make round 2+ opt-in β€” after a push to an already-reviewed PR, the review check auto-passes when the delta is small and touches only files round 1 already saw; anything bigger (new files, >N lines, workflow/security paths) still demands full re-review, and anyone can force one with @claude review. Trade: we accept small unreviewed deltas on already-reviewed files to save ~6–12 min/multi-round PR. This one needs your sign-off β€” it's a quality tradeoff, not a pure win.

To make this 🟒 instead of 🟑: trace whether post-round-2 commits actually touch the lines round 2 flagged. That's scriptable and it's the next cut.

4 Β· Test expiration & lifecycle

πŸ”΄ Don't expire tests by date β€” a calendar-old test can be the one guarding your highest-traffic path. Expire by signal. Concrete mechanism proposal:

πŸ”΄ Honest caution: pruning is a real project with a real downside, and the savings are modest (tests are cheap to hold; they're expensive to run exhaustively β€” which Β§2 already fixed). I'd rank this below shipping the round-2-opt-in and the sibling-adjacency work.

5 Β· Test-per-impact (change file B β†’ test B + siblings)

🟒 This is ~60% shipped as of tonight. The affected-graph model (vitest --changed) now gates both the PR lane and the main lane β€” a change selects only the test files whose import graph touches the diff. That's your "test file B" already live.

πŸ”΄ The open 40%:

What already shipped tonight (so you can see the baseline)

PRChangeMeasured effect
#4444Affected-subset on push:main + hub escalationpush cost 59β†’10 job-min (βˆ’83%)
#4450tscβ†’tsgo type-check~240s β†’ 23s (βˆ’83%)
#4451Native-arm worker build (killed QEMU)18 min β†’ 3.0 min build (βˆ’83%)
#4454Stale-run cancellation on all 16 PR workflows + drift guardβ€”

🟒 End-to-end PR-open β†’ live in prod, measured on a controlled canary through the full gate: 43 min median β†’ 8.2 min (βˆ’81%). The final cost verdict is one complete billed day under the new model (Jul 24) against the $200/mo budget β€” pulled from the billing API when the day closes.

Codex pressure-test β€” my take and Codex's, side by side

Per Gera's ask, the whole design above was fed to OpenAI Codex (independent model, full context, explicitly told not to hand-wave agreement). Verbatim verdict: "The cost posture is sensible, but the system is not production-grade as written." Below is where we agree, where I concede, and where I push back β€” nothing softened.

TopicMy designCodex's takeResolution
Promotion evidence Each push tests its own event.before..sha range; tip-of-main guard prevents out-of-order promotes. πŸ”΄ Sharpest catch: the tip guard proves ordering, not completeness. If push A's run goes red, push B's green run (which tested only A..B) still promotes a deploy containing A's untested regression. Wants a per-artifact deployment cursor: test last-deployed-SHA..candidate, hold promotion if anything in that cumulative range lacks evidence. Concede β€” real gap, adopt. This is the strongest finding in the review. Promote should verify every push:main run since the last deployed SHA is green (or re-test the cumulative range), per deployable.
Hub escalation >200 affected files β†’ full 12-shard suite before deploy. Threshold is a capacity control, not a risk model β€” a 5-line auth/config change is riskier than 500 generated files. Wants risk-class rules (lockfile, workflow YAML, config, env contracts, shared test setup β†’ always full) + full-suite debt that persists to later candidates until a full run passes. Concede both. Risk-class escalation rules are a small ci.yml diff; debt propagation folds into the deployment-cursor work above.
24h nightly backstop Accepted risk window; nightly red pages a human. Not sane alone β€” but notes the ~12 hub-escalation full suites/day shrink the effective gap to ~2h on average. Wants: freshness rule (no green full suite in N hours of active deploying β†’ schedule one), post-deploy smoke per artifact, promotion freeze on red, and push-workflow-absence alerting (the exact dark-deploy failure we already lived through tonight). Adopt smoke + freeze + absence-alert; freshness rule over a fixed midday run. Reject universal auto-rollback β€” Codex itself flags worker/Lambda side effects + Temporal replay compatibility make blind rollback worse; freeze-then-human is right for those.
Auto-merge Armed at PR-open for all PRs; PAT so merges fire push workflows (#4474). Arm at ready, not open (green β‰  author intent). PAT = personnel blast radius β†’ use a scoped GitHub App token. Re-recommends merge queue (merge_group) so evidence covers the actual merge candidate. Wants CODEOWNERS human review on security/CI/billing/migration surfaces. Concede arm-at-ready + App token + CODEOWNERS-on-sensitive-paths (all cheap). Push back on the merge queue: we measured it for a week β€” 66 trains for 63 PRs, it never batched, median time-to-merge 20+ min (ADR-0107). Codex is arguing from first principles without that history; the cumulative-cursor fix addresses the same correctness concern without resurrecting the queue.
Reviewer round-2 opt-in Round 1 mandatory; round 2+ opt-in (48% of round-2s show no follow-up commit). πŸ”΄ False economy: 48% is not low, and "any later commit" is a weak value metric. Wants delta-scoped incremental round 2 (auto-skip only formatting/docs/generated), full re-review forced on new files / deps / CI / auth / migrations, and explicitly: don't classify safety by line count β€” small post-review fixes are where subtle mistakes live. Concede. Tonight is its own receipt: the bot's round-2 on #4444 caught two real bugs in my round-1 fix, one of which would have frozen every prod deploy. Withdraw the blanket opt-in; propose delta-scoped incremental re-review instead.
Test pruning No date expiry; prune by coverage-overlap + failure-history signal. Coverage+history is only candidate generation β€” coverage proves execution, not equivalent assertions. Concrete pipeline: classify by criticality (incident-derived tests tagged), candidate-local mutation testing (retained tests must kill the same mutants), prefer consolidation over deletion, deletion PR carries the evidence, revert-to-restore. Explicit non-reasons: "never failed", "old", "slow", "flaky". Adopt wholesale β€” it's my Β§4 direction made concrete, and candidate-local mutation testing is the missing safety mechanism. Codex agrees a tombstone database would be over-engineered at our size.
Sibling-adjacency (memo Q5) Extend selection to directory siblings, capped. Weak heuristic β€” runs irrelevant tests while still missing remote semantic consumers (Temporal contracts, SQS payloads, generated schemas). Only adopt if shadow data proves recall improves. Concede β€” demote. Replace with selector-correctness telemetry first (shadow-run unselected tests on a sample, track false-negative recall); only ship adjacency if the data says it earns its cost.

Gaps Codex named that the design missed entirely

The production-ready sequence (revised for the review)

  1. Now (correctness, small diffs): deployment cursor + full-suite debt in vercel-promote/deploy gates Β· risk-class escalation rules (lockfile/workflow/config β†’ full) Β· push-workflow-absence alert Β· arm-at-ready Β· App token replaces the PAT.
  2. Next (safety net): per-artifact post-deploy smoke + promotion freeze Β· full-suite freshness rule Β· shard-integrity assertion Β· flake quarantine lane.
  3. Then (efficiency, data-gated): selector-correctness telemetry β†’ sibling-adjacency only if recall data supports it Β· consolidation-based test pruning with candidate-local mutation testing Β· delta-scoped incremental round-2 review (replaces the opt-in proposal).

The three decisions this leaves you

  1. Batched deploys? My read: no, as a cost lever (Β§2). Yes, only if you want a human release gate β€” decide that in the deploy-model doc.
  2. Round-2+ reviews: the blanket opt-in proposal is withdrawn after the Codex review (and after round-2 caught two real bugs in my own #4444 fix tonight). The replacement on the table: delta-scoped incremental re-review β€” auto-skip only formatting/docs/generated, full re-review forced on new files / deps / CI / auth / migrations.
  3. Sibling-adjacency + test pruning? Both buildable, both ranked below the two above because tonight's work already captured most of the cost. Say the word and I'll scope them as their own PRs.
Author: Agent Smith Β· 2026-07-24 Β· Data: GitHub billing API + Actions runs API (145 push-run timings Jul 22–23, 30-push replay, 57-PR review sample). Numbers tagged 🟒 measured / 🟑 estimate / πŸ”΄ opinion. This is a proposal for Gera to correct β€” nothing here is decided.
PropFlow Docs