PropFlow shipping pipeline — end-to-end delivery: current state and how we make it fast
This is the standing reference for how work gets from an opened PR to verified production, what the pipeline measurably costs today, which defects are live in it, and the decisions and fixes that will make it fast. It is meant to be kept current, not read once. The one metric is PR opened → merged, wall-clock, unattended. The target state, in Fede's words: "we should be able to have lots of PRs with low WIP if they are fast to merge." WIP caps are explicitly off the table as symptom-hacks — the thing to attack is slow merges. Every number here comes from the GitHub API or a direct read of the workflow files; estimates are labelled as such.
1 · The metric and the measured baseline
1.1 · Daily baseline (created→merged, all merged PRs)
| Day | Merged | p50 hours | Max hours | Context |
|---|---|---|---|---|
| 2026-08-06 | 3 | 4.5 | 10.8 | pre-crisis |
| 2026-08-07 | 30 | 6.0 | 24.9 | heavy multi-session day |
| 2026-08-08 | 16 | 3.3 | 36.6 | — |
| 2026-08-09 | 35 | 3.0 | 70.9 | repo-wide review deadlock + overnight unjam (18 merges after 22:15 UTC) |
| 2026-08-10 | 16 | 6.2 | 35.6 | after the per-stage "speed fixes"; only 2 merges 06:00–14:30 UTC |
Measurement caveats to fix before drawing strong conclusions: created→merged includes decision-held and abandoned-then-revived PRs, and it does not separate "waiting on machines" from "waiting on a person". Rebuild the metric excluding explicit holds and segment by wait cause. Method: group merged PRs by mergedAt day; hours = mergedAt−createdAt.
What the daily baseline already shows: throughput went down on the day the per-stage speed fixes landed (p50 3.0→6.2 h), while overnight — one shepherd, few concurrent sessions — 18 PRs flowed in 8 hours. Per-stage latency improvements did not translate into end-to-end throughput, and the fixes' own churn (pipeline PRs merging into main) added contention.
1.2 · Where the cycle time actually sits (52 PRs, #5590–#5643, 11,007 check-runs)
| Segment | Share of total cycle | Median per PR |
|---|---|---|
| PR opened → last commit pushed (the lap loop: review rounds, base syncs, rework) | 132.9 h — 91% | 116 min |
| Last commit → merged (the merge tail) | 12.8 h — 9% | 13.8 min |
Sample caveat: #5590–#5643 were all created 2026-08-09/10, so this is a high-throughput two-day window, not a week. Open PRs are excluded from the cycle-time percentiles (their ages ran to 21.7 h and were still accruing).
1.3 · The 21-day distribution (1,289 non-dependabot PRs, Jul 19 – Aug 10)
| Where the 5,469 open-hours went | Hours | Share | Nature |
|---|---|---|---|
| After the final LGTM (approved, idle) | 2,786 | 51% | Human holds, policy waits, idle queue — not automation latency |
| Between first and final verdict (round churn) | 1,609 | 29% | 33% of PRs need ≥2 rounds (median cost +1.4 h); 9% need ≥4 (median 5.2 h) |
| Open → first verdict | 876 | 16% | Mostly draft dwell, not review queue — the first submitted review lands at a median 5.4 min |
synchronize-triggered variant. Dead reviews hit 9% of merged PRs, and 39% needed at least one human @claude review nudge — the pipeline's most common manual intervention, roughly 500 times in three weeks.
2 · The end-to-end map, and the defects living at each seam
The ladder is open → review starts → verdict posts → verdict becomes the required status → tests green → merge-eligible → merged → verified. Almost every observed failure lives between stages, not inside them. Each seam below lists how it breaks, then the mined defect claims attached to it — 13 distinct claims recovered from 947 session transcripts over a 72-hour window, deduped and each checked against the live workflows and the GitHub API rather than inferred from the transcript.
Status vocabulary: CONFIRMED verified against the live repo · PARTIAL partly remediated · UNVERIFIED plausible, carded, not reproduced · FIXED closed by a merged change.
run-tests label.
- D4 · The
reviewcheck ends up ABSENT — not skipped — on workflow-only PRs, becauseworkflow_dispatchattaches check-runs to the dispatch ref rather than the PR head. CONFIRMED — permanent block, and the PRs that hit it are exactly the CI-repair PRs, so the repo periodically loses the ability to repair itself. #5622 fixed this shape forUnit Tests;reviewnever got the same treatment. On 08-09 landing the unblocking fix required disabling branch protection.
gh workflow run claude-code-review.yml -f pr_number=N re-fire; the watchdog (#5617) pages after 24 h, far too slow for daytime work. (b) The re-fire path itself was broken for ~2 days by a .draft // true jq bug (#5581) that treated every ready PR as a draft; fixed by #5620. (c) On the open backlog, five PRs waited on verdicts that never came — worst case 18,109 min ≈ 12.6 days — and the watchdog's own prescribed re-fire command was verified to do nothing on #5524, so the documented recovery path is itself broken.
- D5 ·
@claude reviewcomments cannot refresh the required check — they route through a workflow that never writes thereviewcontext, so every "just ask for a re-review" is a silent no-op against the gate. CONFIRMED — no fix in flight; documented inclaude-review-recheck.yml's own header and on cardnfbjyxE9. The recovery that works is re-dispatching the review workflow at the same SHA; the recovery most sessions reach for (draft → ready) works but disarms auto-merge and costs a full merge cycle. - D11 · The review-verdict watchdog false-pages forever on skip-passed PRs that receive any later commit, re-posting every 15 min. CONFIRMED — alert fatigue on the one alarm built to catch a silent stall. Issue #5633, opened one day after the watchdog shipped (#5617). No fix in flight.
review status. Breaks: (a) the encoder requires the <!-- claude-review-summary --> marker comment; a dead review leaves the old comment and the encoder fails closed. (b) Formal CHANGES_REQUESTED reviews stick to old SHAs: 🔴 rounds submit REQUEST_CHANGES, later 🟡 rounds submit COMMENT which never clears the earlier CHANGES_REQUESTED — reviewDecision stays CHANGES_REQUESTED forever and blocks merge with every check green. #5632 sat invisible in this state; the remedy was dismissing the stale reviews via API with an audit-trail message (14 reviews were dismissed across 10 PRs in the two-day window). The second reviewer bot (propflow-code-reviewer[bot], appeared 2026-08-10) files formal CHANGES_REQUESTED too and will reproduce this wedge. UNFIXED
- D2 · The required
reviewcheck carries a stale verdict forward onto every new head SHA — by design, and the carried verdict emits no review event, so it also fails to start the thing it used to start. CONFIRMED — two harms: unreviewed code satisfies branch protection on an old verdict, and the missing review event is the direct feeder of D1. Verified live on #5625:review= success, title "Carried-forward review verdict", summary stating the carried verdict was 🟡 from an earlier SHA. Across the two-day sample, 96 of 179reviewcheck-runs (54%) are carried-forward — the majority of greenreviewstatuses were not a review of that SHA. Mitigated for merges only (auto-merge disarms on push); branch protection is not mitigated. Counterexample 2026-08-10 21:00Z: #5625 auto-merged ~50 s after a fix push, on a round-1 verdict from a prior SHA — the disarm-on-push mitigation did not fire (worth checking against the #5639 guard changes). #5610 would have closed it and was closed unmerged 08-10. - D9 · A stale
reviewcommit status — distinct from the check-run — can strand atfailurewhen a dispatch is cancelled, blocking a PR that later goes green. UNVERIFIED — cardedN9FRPNSYwith two proposed fixes, not started. Dangerous because everyone reads the check-run and nobody looks at the commit status.
claude-review-recheck.yml carries a green check-run forward across syncs so merge isn't blocked — but that green is a carried-forward verdict, and code merged under it was never re-read (see D2 above; Fede declined per-push re-review on cost grounds, a known accepted tradeoff at the time). Meanwhile the watchdog requires a review pinned to the head SHA, so a fix-push without re-review pages at 45 min — accurate but noisy. Note for anyone analysing this: every bot review event is filed as COMMENTED and verdicts live in the review check-run, so any analysis keyed off reviewDecision reads zero rounds and undercounts rework roughly 4×.decide-unit-tests-conclusion.sh) is new as of #5632 and has had little production exposure. (c) The Unit Tests umbrella check is not a job at all — it is a check-run POSTed by hand from ci.yml — which is the root of the largest cluster of defects in the ledger.
- D1 · The required
Unit Testscheck parksin_progresswith no conclusion and nothing guarantees it ever closes. CONFIRMED — the writer has an explicit branch that POSTs/PATCHes a status with no conclusion for thedeferredandcappedmodes; a check-run in that state renders as a spinner and blocks merge even when every real test job passed. Measured: 56 of 152 umbrella check-runs (37%) satin_progresswithconclusion: null; 42 were eventually cleared by a later run after a median 94 min / mean 150 min / p90 361 min block, and 14 were never resolved on their PR at all. Stranded runs carry the output title "Deferred until review" / "Full suite deferred until review". 6 of 19 open PRs were parked on it at the time of mining, one since 08-06 (~4 days); it hit 8 PRs in a single session. Concentration: #5631 (17 stranded laps), #5612 (7), #5617 (6), #5606 (6). Issue #5534 (08-07) is a near-verbatim independent diagnosis; cardnfbjyxE9. No fix in flight — the remedy in practice is a manual label toggle that is a coin flip (and re-adding a present label emits nolabeledevent, so it needs an off/on toggle). Not fixed by #5632: five of the six then-stuck check-runs were created after #5632 merged. - D8 · A cancelled run leaves the umbrella check exactly as it was — the reporter deliberately writes nothing when superseded, so a pending state survives the run that should have resolved it. CONFIRMED — 48% of
ci.ymlruns are cancelled bycancel-in-progress(48 of 100 in one 2.5-hour window). This is the mechanism behind most instances of D1, and shares its fix surface. No fix in flight. - D6 · The deferred
Unit Testsreporter can overwrite a real pass, leaving the required check green with a summary saying the lane never ran. CONFIRMED — a false green, which merges code that ran no unit tests. Worse than a block, because nobody investigates a green. Issue #5556 open, no fix in flight. - D7 · Mixed docs+code PRs are merge-eligible for ~16 minutes on a green
Unit Teststhat measured nothing — the docs stub races the realci.ymlreport. CONFIRMED — narrow but real false-green window on every mixed PR, and auto-merge fires inside it. Issue #5532 open, no fix in flight. - D12 · A unit test fails ~1 in 3 standalone on clean main (
outlook-webhook.test.ts) — suspected zero-delay timer letting an in-flight handler leak into the next test. CONFIRMED BY REPRO — random red on unrelated PRs, each costing a re-run lap plus the time to decide it is noise. CardedVN47ktKmwith the suspected line identified. Not started. - D13 · A PR whose affected-test selection exceeded the cap skipped the lane and reported
successanyway — green with zero unit tests executed, the most dangerous false green in the set. FIXED by #5530, merged 2026-08-07 (issue #5518).
auto_merge_disabled events across 30 PRs against 39 auto_squash_enabled — ≈2.7 disarms per arm (max 16 on #5631). The "sweeper" (a scheduled job that syncs and re-arms green, non-hold PRs) is UNBUILT. (c) Auto-merge cannot arm on .github/* PRs (a deliberate guard), so workflow PRs need a live human or agent to press merge and orphaned ones rot. (d) Session death orphans PRs — there is no owner of last resort; "not mine" politeness between sessions left finished work unowned for 21 h.
- D3 · Strict up-to-date branch protection + a fast main = a re-verification treadmill: every merge to main knocks the other open PRs to BEHIND, and satisfying that mints a new SHA that re-runs the required suite. CONFIRMED · 2 of 3 gears removed — 148 of 378 commits (39%) in the two-day sample are merge-from-main syncs; at the measured mean CI lap of 17.9 min that is ≈44 h of PR wall-clock re-proving unchanged code ESTIMATE. 224 commits landed on
mainover 08-03→08-10 (≈30/day). Protection confirmedstrict:true, required contextsBuild, Type Check, Unit Tests, review, no merge queue configured. Worst offenders: #5618 (15 syncs), #5612 (12), #5631 (11), #5606 (10), #5613 (9). Gears removed: #5632 (test lane no longer waits for a verdict) and #5639 (round 2+ re-reviews verify blockers only). The sync-and-re-verify gear itself remains — see §7 decision A. - D10 · A DIRTY merge state makes every
pull_requestworkflow silently not run — no red check, just absence, whilepull_request_targetworkflows still run and mask it. UNVERIFIED — a conflicted PR looks tested and is not: zero CI, zero signal. CardedRAp5Afg1(08-01). Not started. - Approved-and-green but never armed → rots into conflicts. Time-to-conflict on this repo is 2–7 days. #5576 collected 12 APPROVED verdicts and was still unarmed and BEHIND; #5634 was double-approved at 05:08 and behind by 18:26; #4868 was approved Aug 3 and DIRTY after 7 idle days. An unarmed green PR reliably converts into a conflict PR. ~24 PR-days in the open backlog.
- Deliberate holds with no expiry or owner.
hold-for-reviewsat on 11 of 22 open PRs and nothing ever removes it: ~72 PR-days lost, the single biggest backlog bucket. #3087 waited 36 days on a "cut day" never scheduled; #4705 sat 14 days pending an audit nobody ran; #5393 was held on a question escalated "for a person" that no person answered.
merge-and-reap.sh, so worktrees and branches accumulate and need catch-up reaps; reaping on the merge webhook instead of at merge-command time would close this.
- Dependabot: one shared lockfile drift. All three open dependabot PRs fail on the same
npm cierror (Missing: gcp-metadata@7.0.1 from lock file), unrelated to the bumped packages. One lockfile fix clears all three; #5560 additionally cannot self-rebase (manually edited). ~9 PR-days.
3 · Why per-stage fixes didn't move the number
main knocks every open PR BEHIND (~30×/day). The sync push produces a new head, which (a) disarms auto-merge, (b) invalidates the verdict — so the review check either re-runs or is carried forward, and (c) writes a Unit Tests umbrella that is deferred until review and therefore has no conclusion. All three required contexts now depend on a review event that a merge-from-main commit did not earn. That is one loop, not four independent bugs, and it is why #5631 needed 23 review laps and 11 syncs for a change that had stopped moving long before.3.1 · Ranked bottlenecks, by measured wall-clock cost
| Rank | Bottleneck | Measured cost | Hard evidence |
|---|---|---|---|
| 1 | The BEHIND treadmill (D3) — strict: true on a main that moves ~30×/day | 39% of all commits are syncs; ≈44 h of re-proving unchanged code in two days ESTIMATE | See D3 above: 148/378 sync commits, no merge queue, worst offender 15 syncs on one PR |
| 2 | The Unit Tests umbrella stranding with no conclusion (D1 + D8) | 37% of umbrella runs stranded; median 94 min block, p90 361 min; 14 never resolved | Status-only write on the deferral branch, plus the write-nothing-when-superseded path |
| 3 | Review-round churn on non-production files | 20 PRs had rework after their first verdict; files touched: 78 test, 69 production, 22 harness/CI, 4 docs — 60% non-production | Generalises the #5638 pattern (feature frozen after round 1, five more rounds of harness hardening). Clearest: #5609 (9 rework commits, zero production files), #5632 (7, zero production), #5608 (3 commits, 6 test files, zero production) |
| 4 | Auto-merge silently disarming on new heads | 105 disarms across 30 PRs vs 39 arms (≈2.7:1); 149 "Auto-merge on green" runs fired chasing it | Nothing re-arms mechanically — this is precisely the unbuilt sweeper |
| 5 | Carried-forward verdicts standing in for real ones (D2) | 96 of 179 review check-runs (54%) carried-forward; 45 "Bot APPROVED — encoder override"; 26 concluded failure | Median 3.5 review laps per PR; #5631 alone consumed 23 |
| 6 | Empty/errored review runs | 4 instances in one day; dead reviews hit 9% of merged PRs over 21 days | Each silently blocks BOTH gates: the full suite only starts on a verdict event, so a dead review starves the test lane too |
Context on volume: claude[bot] filed 361 review events and propflow-code-reviewer[bot] 140 across the two-day sample. CI lap wall-clock: 200 laps, median 10.2 min, mean 17.9, p90 38.4. The 12-shard full suite alone accounts for ~2,860 shard-minutes; Unit Tests (affected) runs in a median 2.1 min — the affected lane is cheap and is not the problem. What actually merged things on the measured day: every product PR that landed (#5628, #5630, #5640, #5641) was landed by armed auto-merge catching a green window; zero hand-timed merges succeeded.
3.2 · Two failure modes worth naming separately
- Auto-merge can fire mid-review. Native auto-merge merged #5630 at 16:28Z while the incremental review was still producing real findings; rounds 7–9 landed post-merge, one of them life-safety (a swallowed write that could release an agent loop onto a gas-leak thread). The fixes had to ship as a follow-up PR. Remedy: don't arm until the review declares convergence, or disarm on any in-progress review run.
- Designed-red reads as failure. The by-design Specialist drift red and the eval budget-skip repeatedly produced failure-shaped events, drew human attention, and caused one full misdiagnosis. Remedy: neutral/skipped conclusions, or a distinct check name for designed-reds so rollups stay honest.
- Two sessions racing one PR. #5631 and #5638 each had two sessions pushing base syncs or holding uncommitted worktree edits concurrently — duplicate laps, and a near-miss of shipping over in-flight work. Remedy: a per-PR ownership claim (comment marker or label) that other sessions honour.
4 · How we verify an improvement — the probe protocol
Pipeline changes are claimed, not proven, unless they move open→merged on a real PR. Fede's standing requirement: "prove that you can make it much faster and smoother with dummy PRs." A worktree is prepared for this: ~/.claude/propflowai-probe-1, branch fede/pipeline-probe-1 off origin/main.
- Probe unit: a PR with one trivial substantive change (e.g. a self-contained test file
src/__tests__/pipeline-probe.test.ts) so it takes the REAL path — review plus the affected lane — not the docs fast-path. Honest title ("probe: e2e timing") and an honest body stating the plan, so the reviewer doesn't block it on process. - Measure per probe, using GitHub API timestamps rather than wall-clock guesses: open → review start → verdict comment → review status green → tests green → merge-eligible → merged. Arm auto-merge at open. Zero manual intervention; every manual touch invalidates the probe and must be recorded.
- Run matrix: (a) 2–3 solo probes on a quiet main — the clean path; (b) 2 concurrent probes — contention; (c) 1 probe pushed while another merges — the treadmill cost directly, measured as time from knocked-BEHIND to merged.
- Success bar (proposed, Fede to confirm): solo probe open→merged < 30 min unattended; green→merged < 5 min; a knocked-BEHIND armed probe recovers with no manual touch.
- Cost note: each probe burns one large-tier review and a CI lap. August CI spend was already 68% of budget by day 9 — batch probes, don't spray them.
- Run it before and after any structural change (especially the §7 decisions), and keep the change only if the solo number drops.
5 · Recent incidents and the lessons that stuck
Real events from the 2026-08-06→10 stretch, kept because each one produced a rule. Read them as failure modes the pipeline and its operators are prone to, not as history.
.draft // true — jq treats false as absent — so the documented recovery path treated every ready PR as a draft and was non-functional for every non-draft PR until #5620 fixed it. Multiple PRs stalled overnight. Lesson: recovery paths need their own test; a broken recovery is invisible until the thing it recovers from happens.enforce_admins was lifted for 3.7 s to force-merge #5622 over an incomplete required check during a stabilise-ASAP push. It was disclosed in-session and logged in the org audit trail — but the PR body still says "no bypass was attempted" and the team update omitted it. The governance audit called the execution clean and the disclosure record not. Lesson: the disclosure has to land in the artifact people will read later (PR body, team post), not only in the live conversation.6 · Current state (as of 2026-08-10 ~21:00 UTC)
| Item | State |
|---|---|
| #5632 parallel test lane | MERGED 14:35Z — lane starts on the PR event; conclusion chokepoint I1–I12 |
| #5639 review-round convergence | MERGED 20:00Z — round 2+ verifies blockers only; advisory nits can't gate; degrade→round 1. Shifts advisory-only rounds to formal APPROVE → auto-merge-clean (disclosed in the PR body) |
| #5587 reviewer-prompt injection fix | OPEN, green-pending — re-derived and conflict-resolved against #5639, 410 tests green, review running; merges on green under the standing grant |
| #5625 #5626 #5627 (orphaned lead-pipeline fixes) | SYNCED + AUTO-MERGE ARMED — land on green |
| #5631 (18 review rounds) | AUTO-MERGE ARMED — treadmill victim; drift-check red is by-design |
| #5638 #5642 | actively iterating — left alone |
| Sweeper (owner of last resort) | NOT BUILT — spec: scheduled job syncs and arms any green, non-hold, fede-authored PR untouched for N hours |
| Stale-CHANGES_REQUESTED wedge | NOT FIXED — see §2; the second reviewer bot makes it more likely |
Unit Tests umbrella stranding (D1/D8) | DIAGNOSED, NOT FIXED — root cause pinned (status write with no conclusion); issue #5534, card nfbjyxE9; highest-yield fix on the board |
| False greens D6 (#5556) and D7 (#5532) | OPEN — issues filed, no fix in flight |
#5610 (a green review must mean that SHA was reviewed) | CLOSED UNMERGED 08-10 — the loophole it targeted is still open; see §7 decision B |
| Dependabot lockfile drift | OPEN — one npm ci error blocks all three PRs; one repair commit clears them |
| Overnight governance audit | published: /a/overnight-audit-2026-08-10.html — carve-out (#5623) legitimacy unconfirmed; #5465 prod cron merged past even the carve-out and partially failing daily at 11:00 UTC |
7 · Decisions for Fede
Four decisions gate the structural work. Everything else in §8 can proceed without them.
A · The remaining treadmill gear — what a base-only sync should cost
With #5632 and #5639 merged, a base sync no longer costs a review round or a verdict wait. What remains is that a sync re-runs the four required contexts, and 39% of all commits written are those syncs. A merge queue is off the table — already tried, no measurable difference (settled).
A1 · Let a required check survive a base-only sync RECOMMENDED
When the new head differs from the reviewed head only by main's commits — the diff against base is byte-identical — carry the existing green forward instead of re-running. Carded as T4LCIOrq. It attacks the actual waste: nothing about the change under review changed.
Risk, stated plainly: this is semantically the same move as the stale-verdict carry-forward in D2, so it must be built with the diff-vs-base unchanged proof, not a bare carry-forward. It pairs naturally with decision B — the two together give "cheap when nothing changed, rigorous when something did."
A2 · strict: false repo-wide
The treadmill disappears entirely and armed green PRs merge without a re-lap. Cost: a PR can merge against a base it never tested against, and you find out on main — with several agent-authored PRs touching the same CI surface, this trades a latency problem for a correctness problem. ~1 h to change, immediately measurable.
A3 · strict: false by default, kept true only for .github/** and infra paths
A2's throughput where the volume actually is (product PRs, where all 148 syncs were spent), while the paths that can break everyone keep the up-to-date guarantee. ~2 h via a path-scoped ruleset. This is the strongest option if A1's byte-identical proof turns out to be hard to build safely.
A4 · Accept it and shrink the lap instead
Nothing structural; keep cutting the 14–34 min green lap. Honest option — the two gears removed already cut the worst of it and median push-to-merge is 13.8 min. Choose this if the felt problem is already gone.
Whichever is chosen, gate it on §4: run the probe matrix once before and once after, and keep the change only if solo open→merged drops.
B · What a green review check is allowed to mean
#5610 was built to make a green review check mean "this SHA was actually reviewed", was held for review, and was closed unmerged on 08-10. The loophole is still open, and it is now measured: 54% of green review statuses in the sample were carried forward from a different SHA.
B1 · Revive #5610's intent — a green check requires a verdict at that SHA RECOMMENDED
Branch protection stops being satisfiable by a carried-forward verdict; auto-merge already behaves this way, so this makes the gate agree with it. The cost is that every fix-push needs a real re-review — far cheaper now that #5639 made round 2+ incremental. The earlier cost-based decline of per-push re-review was made before that change and before the 54% number existed.
B2 · Keep carry-forward, but make it visibly provisional
Leave the check green so the PR isn't wedged, but have the carried-forward path emit the event that starts the test lane, and mark the check title so a reader knows nobody read this code. Cheaper; keeps the false-green risk but removes the compounding into D1.
B3 · Leave as-is
Defensible only if auto-merge's disarm-on-push counts as a sufficient backstop. It is not a backstop for a human clicking merge on a green PR.
C · Batching windows
Independent of A: with ~30 merges/day, each merge invalidates every other open PR.
C1 · No batching (today's behaviour).
C2 · Merge in windows
Land accumulated green PRs on the hour, so a PR takes one sync hit per window rather than one per merge.
C3 · No batching — fix the umbrella conclusion and auto-re-arm first, then re-measure RECOMMENDED
Batching adds latency by construction and is only worth it if a sync is still expensive after §8's cheap fixes land. Decide after, not before.
D · Governance and capacity items
- #5623 carve-out: confirm you directed it (and then cite it in the PR), or revert the CLAUDE.md line and retro-review the seven merges made under it.
- #5465 tenant-sync prod cron: review on the merits or disarm pending review — it merged 10 s after green and its daily 11:00 UTC run is failing for one of two properties.
- Review capacity: the shared review queue is the other genuine bottleneck under load. The hosted Code Review App spike is the standing alternative — fund it or explicitly park it.
- Probe success bar: confirm or adjust the §4 thresholds, since everything structural will be judged against them.
8 · What to do next, in order
8.1 · Do these without asking — no decision content, high yield
| Fix | Attacks | Effort ESTIMATE | Expected effect |
|---|---|---|---|
Write a conclusion on the deferral branch — emit completed/neutral (or success with a "deferred" title) instead of a bare in_progress; and close the umbrella on run-cancelled | D1, D8 | ~1 h, single file (the ci.yml chokepoint) | Removes a median 94 min block from 37% of all laps. Highest ratio on the board, has an open issue and a pinned root cause, needs no decision |
| Fix the two false greens — the deferred reporter overwriting a real pass (#5556) and the docs-stub race (#5532) | D6, D7 | ~2 h | Strictly more dangerous than the blockers: a block gets investigated, a green does not |
Give the review check the treatment Unit Tests already got so a skip-passed review lands on the PR head rather than the dispatch ref | D4 | ~2 h, one workflow | Restores the repo's ability to repair its own CI without disabling branch protection |
| Fix the re-fire command the watchdog prescribes (verified broken on #5524), then let the watchdog auto-re-fire dead runs at 30–45 min instead of only paging at 24 h | D5, dead reviews | ~2 h | ~40 PR-days of current backlog plus the 39%-of-PRs manual-nudge tax |
Arm-on-approve, and re-arm on synchronize when a PR was previously armed and is non-hold | Armed-but-stale; never-armed | ~2 h | Kills 105 manual re-arms and stops green PRs rotting into conflicts (2–7 day half-life); subsumes most of the sweeper's job |
| One lockfile repair commit (regenerate against main, let dependabot rebase) | Dependabot drift | ~30 min | Clears 3 PRs immediately |
8.2 · Then, in this order
- Run the probe protocol (§4) to establish the honest post-fix baseline before any structural change.
- Build the sweeper / owner of last resort — a scheduled job that syncs and arms any green, non-hold PR untouched for N hours. Verdicts that never arrive plus overnight dead air were ~85 h of waste in a single measured day.
- Fix the stale-CHANGES_REQUESTED wedge — auto-dismiss superseded formal reviews when a newer verdict exists at head, or stop submitting formal REQUEST_CHANGES at all. Needs no decision, and the second reviewer bot makes it more likely.
- Holds must carry an owner and an expiry. A
hold-for-reviewlabel with no named unblocking decision goes stale forever (~72 PR-days, the biggest backlog bucket). Mechanic: a weekly sweep posts every held PR >72 h with the question that unblocks it, as multiple choice, in one message. - Post-LGTM idle alarm. 51% of all open-hours are after approval. Any PR green+approved+unmerged for >2 h gets one consolidated ping naming what it is waiting for (arm, human floor, hold label).
- Scope review depth for instrument-only diffs — when a round's delta touches only tests, harness, or CI, run the shallow lens rather than a full read. 60% of post-verdict rework is non-production; this cuts round count and review spend together (~4 h, reviewer prompt plus a path classifier).
- Verify D9 and D10 (stranded
reviewcommit status, DIRTY PRs running zero CI). Both are cheap to check and both are in the false-signal family. - Bring §7 to Fede once, as multiple choice with probe data attached — not as an open-ended wait.
One note in favour of the review rounds, so the cuts stay honest: the reviews catch real defects — vacuous tests, a DDB escape, a brittle string-pin, a half-written close-out, and the mid-review auto-merge case in §3.2 that was life-safety relevant. The waste is in re-verification laps and non-convergence, not in reviewing itself.
9 · Method, sources, and known limits
- Daily baseline (§1.1): merged PRs grouped by
mergedAtday via the GitHub API; hours = mergedAt−createdAt. - Two-day sample (§1.2, §3.1): every PR #5590–#5643 (52 PRs — 36 merged, 9 open, 7 closed-unmerged): PR and commit metadata via
gh pr view --json, timelines via/issues/{n}/timeline, check-runs via/commits/{sha}/check-runsacross 232 head SHAs, branch protection via/branches/main/protection, merge cadence viagit log origin/main --since. - 21-day sample (§1.3): GraphQL over all 1,289 non-dependabot PRs merged Jul 19 – Aug 10, plus REST timelines for every open PR. Verdict = a bot review/comment carrying 🟢/🟡/🔴/APPROVED (96% coverage).
- Defect ledger (§2): 947 session transcripts from a 72-hour window (
~/.claude/projects/**/*.jsonl), deduped, then each claim checked against the Trello board, open GitHub issues, and direct reads of.github/workflows/andscripts/ci/atmain. Statuses reflect the live repo, not the transcript. - Not confirmed: the
@claude reviewcomment path failing to write the check could not be tied to a specific one of the 58issue_comment-triggered runs in the two-day window — treat the §2 seam account as the better source. Attribution of the 105 auto-merge disarms is a GitHub API artifact (all credited to the arming account) and must not be read as manual intervention. Any analysis keyed onreviewDecisionundercounts rework roughly 4×, because verdicts live in comments and check-runs rather than review states. - No tenant, resident, or customer data was read at any point in producing this document.
Keep this document current: when a defect in §2 is fixed, change its status rather than deleting it, and when a §7 decision is made, record the choice and the date next to it.