Always Shipping

Why PRs are not green, not merged, or not worked on at all while nobody is at the keyboard — and what rules replace the ones that cause that.

Decided 2026-09-17 — Fede adopted all of it except the settings registry (section 5, "Hygiene without a flag platform", first bullet), which is deferred: "too much for now".

Contents

  1. What the 30 days show
  2. Why last night specifically
  3. The thirteen holds, and what to do with each
  4. The rules that replace "end of night"
  5. Risk without flags: the three-switch system
  6. Options for Fede
  7. What was done today
  8. PR flow, 09-15 to 09-28
  9. PR cycle retro, 2026-09-28
  10. Feature inventory, Sep 15–29 2026 — what's on or off, per company
  11. Retro 2026-09-29 — "finish all WIP tonight" ended with 15 open PRs

TL;DR

1. What the 30 days show

The machine ships. The problem is the night and the morning baseline, not throughput.

Fede-authored PRs, 2026-08-18 → 09-17
Opened / merged / closed unmerged / open now1,607 / 1,439 / 161 / 7
Median time to merge37 minutes
90th percentile4.4 hours
Merged within 24 hours98.5%
Merged outside 7am–7pm Denver44%

So "always shipping" is already mostly true. Three numbers explain the mornings that feel bad:

SymptomNumber
PRs ever held by the auto-merge guard512 of 1,607 (32%)
Top hold reasonsdangerous-diff path 179 · 🟡 changes suggested 132 · changes-requested standing 50
PRs needing more than 3 review rounds33.5%
PRs over 600 lines613 (38%), median 4 review rounds
Nights with a ≥3-hour dead stretch, last 147
Finished-subagent notifications unanswered for 5+ minutes22,581 of 66,931 (34%)

Read together: a third of PRs get held, a third need four or more rounds, and half the nights the fleet stops before those are cleared. Whatever is unfinished at the dead stretch is what Fede finds at 7 am. Seven open PRs this morning was a normal night, not a bad one.

2. Why last night specifically

Timeline (Denver time):

Root cause A, mechanical. Each session is woken by a small waiter process that watches the GitHub event file. Waiters did not die when their turn ended. They lived up to 24 hours holding the session's "I already have a waiter" lock, so the next real waiter could not arm, and the stale one consumed each verdict and woke nobody. Found on the machine: 11 waiters whose Claude process had exited days earlier, some since September 10; the machine-wide duty lock held by a waiter from a turn that ended at 8:57 pm. Fixed today in the waiter script: a waiter exits within five minutes of its session ending, and a lock held by an orphan is taken over.

Root cause B, behavioral. Seven of the last fourteen nights have a three-hour-or-longer dead stretch, always after midnight. In 30 days, one in three "subagent finished" notifications (22,581 of 66,931) went unanswered for more than five minutes. The 25 longest silences were sessions that wrote a done-or-parked summary, or asked Fede a yes-or-hold question and stopped for a day. None were errors or limits.

Root cause C, structural. Thirteen distinct mechanisms can stop a green PR from merging without a person. Each is reasonable alone. Together they mean a PR needs a live session to clear it several times, and a sleeping session clears nothing.

3. The thirteen holds, and what to do with each

HoldKeep?Change
Changes-requested review standingkeepThe session that pushes the fix dismisses the stale review in the same turn. Never left for morning.
No verdict on current headkeepAutomatic. Nothing to do.
🟡 changes suggestedchangeNo longer holds. 🟡 = merge, then open one follow-up issue per finding. Only 🔴 blocking holds.
Stacked basekeepAutomatic.
Dangerous-diff pathkeep, narrowOnly auth, IAM, middleware, secrets, the guard itself. Admin routes under /api/admin behind admin auth are not dangerous.
Merge-evidence (green with zero tests run)keepThis is the one hold that catches real risk.
DraftkeepDrafts are not work in progress. A draft older than 24h is closed by its owner.
hold-for-review labelkeep, Fede-onlyUnchanged. Sessions never apply it.
Stale approval after pushkeepAutomatic.
Hand-armed auto-merge disarmed by eventchangeThe guard re-arms itself on a clean verdict; sessions must not treat "not armed" as a hold.
Red main freezekeepDriver rule already exists.
CI redkeepFix.
Review workflow self-modifykeepRare.

4. The rules that replace "end of night"

  1. A session that owns an open PR does not end its turn until the PR is armed for auto-merge or merged. Blocking waits happen inside one long command, not by ending the turn.
  2. A finished subagent is consumed by its parent within the same turn. A parent that has nothing to do with the result still says so and continues.
  3. A yes-or-hold question to Fede never stops the session. It posts the one-line ask and keeps working everything that does not depend on it.
  4. A sweep session wakes owners; it never takes over or splits a PR. A PR already under review merges as-is.
  5. Morning baseline: zero open fede-authored PRs older than 12 hours that are not armed. Anything else is a defect the owning session explains in one line.

5. Risk without flags: the three-switch system

Fede's constraint: ship aggressively, keep customers stable, no proliferation of forgotten flags.

Rule: there are exactly three places a behavior can be switched, and they are all product configuration, not engineering flags.

Switch kindWhere it livesWho flips itLifetime
Company live switch (claraLive)customer pageFedeforever; it is the go-live
Company setting (e.g. reminders opt-in, vendor calendar capture, lead source)customer page, settings cardops or the customerforever; it is a product feature
Property setting (e.g. per-property reminder recipient)property pageops or the customerforever

What is banned: environment flags for behavior (*_ENABLED), code-level release toggles, allowlists in code, "temporary" gates. Today there are 5 env flags and 6 org/property settings. The 5 env flags get a removal PR each or become a company setting.

How a risky change ships:

  1. It ships merged and deployed the same day, dark, meaning: off for every company by default because it is a company setting whose default is off.
  2. It is turned on at a test company (Fairhaven, Willows) by the session, with a proof on the tracker.
  3. Fede turns it on per customer from the customer page. That is the only "flag" anyone ever manages, and it is visible on the page the team already reads.

Hygiene without a flag platform:

6. Options for Fede

Decision: A, minus the settings registry.

  1. A. Adopt sections 3–5 as written (recommended). Dangerous-diff floor narrowed, 🟡 no longer holds, three-switch system, settings registry.
  2. B. Adopt 3–4 only; keep flags as they are. Fixes the overnight stalls, leaves the flag question for later.
  3. C. Keep holds as they are; only fix the waiter defect and the end-of-night rule. Least change, most mornings like today.

7. What was done today

PR flow, 2026-09-15 to 09-28: why WIP is high and merge-to-live is slow

Last week vs. the week before

MetricPrior week (09-15–21)Last week (09-22–28)
PRs merged1,4321,915
Open→merge, 90th percentile (hours)6.44.8
Merge→live, 90th percentile (minutes)2071
Share of PRs needing 3+ review rounds44%30%

Review is not the problem — last week beat the prior week on every review measure while shipping 34% more work. What got slower is the last mile: once a PR merges, it now waits for a 30-minute batch window before it goes live, so a typical PR now takes about 1.4 hours from opening to live, up from 1.0 hour.

Where the time goes (two weeks combined, PR-hours)

Time sinkPR-hours lostShare of open→merge time
Waiting for the first review verdict1,0549%
Fix loop (rounds that came back red)2,94124%
Final stretch — merged within 24h of going green3,36127%
Final stretch — sat green >24h unmerged ("parked green")4,65638%
Merge to live (separate from the above; waiting on the deploy batch)1,586—

The single biggest sink, parked-green PRs, is only 40 PRs — all in Gera's lane, none armed for auto-merge, 4 carrying a hold-for-review label.

Are 50+ comments on a PR normal, or a bug?

Mostly normal. A PR with 50+ comments has, by design, about 10 review rounds — each round writes a run summary, a verdict summary, and inline findings, and the ≥50-comment PRs are big (median 1,570 lines changed). Reading a sample of what triggered the blocking reviews on these PRs: about 60% were real code defects worth catching. Roughly 20% was avoidable noise from the review system itself — mainly the plan reviewer posting a brand-new comment on every push instead of updating one (13% of all comments on these PRs, concentrated in a spike from 09-16 to 09-19 while its red verdict held merges), plus sessions pinging for a review that was already running (another ~7% together with the "already running" replies). One genuine review-system bug sat in the window and was fixed on 09-18: a blocking review that quoted an earlier green verdict was being misread as an approval.

What we couldn't measure

Proposed — pending review

  1. Refuse a PR over ~600 lines until it's split. Big PRs review much worse (600+ lines average 3.8–5.8 rounds vs. 1.8 for 100–300 lines). Estimated effect: about 18% fewer review rounds across the two weeks, and the worst-case (90th percentile) open→merge time drops from about 5 hours toward about 4.
  2. Clear or arm Gera's parked PRs the same day, through Smith. Targets the 4,656 PR-hours of green-but-unmerged work above. Estimated effect: open PRs drop from about 92 today to about 50, by closing or landing the ones over a week old and the ones on hold.
  3. Block re-review until the flagged lines actually change, and make the plan reviewer edit one comment instead of posting a new one on every push. Estimated effect: removes the roughly 28% of blocking rounds that just repeat an unfixed finding from the previous round, and cuts comment volume on big PRs by about 20%.

Not ranked, but worth a decision: the 30-minute deploy batch costs about 25 minutes median per PR (merge→live went from 13 to 37 minutes median). Deploying immediately whenever nothing has shipped in the last 30 minutes would win most of that back without losing the batching benefit.

PR cycle retro, 2026-09-28

Headline numbers

MetricPrior week (09-18–24)This window (09-25–28)
Open→merged, median / p9056 min / 6.0 h31 min / 4.0 h
Merged→live, median / p9015 min / 48 min43 min / 1.2 h (worse)
Review rounds per PR (mean)3.12.2
PRs open >24 h2 merged2 merged + 5 still open

The typical PR is faster than last week. What Fede sees is the tail: 50 PRs (12%) took more than 4 hours, and they hold most of the time — 823 PR-hours spent open in the window, 246 (30%) of it PRs sitting as drafts.

Top 3 causes of the tail

  1. Parked as a draft waiting for Fede's go — 15 PRs, 277 PR-hours. Merging would have turned something on at a customer (self-guided tours, staff texting, a stage chain), so the session drafted it instead of cutting a default-off switch first.
  2. Review-round fix loops — 12 PRs, 83 PR-hours of real defects, concentrated in big PRs (PRs over 1,000 lines were 7% of all PRs but produced 38% of all 🔴 findings and 45% of fix-loop hours).
  3. Conflicts / re-merge with main after green — 11 PRs, 72 PR-hours; the guard's "main moved" hold fires even when the only change is a row added to a drift-test inventory.

The 8 changes

#ChangeRule textEst. PR-h saved/weekWhere it livesStatus
1No draft waits for Fede"Never park a PR as a draft for Fede. If merging would turn anything on at a customer, the PR is cut wrong: first merge a company/property setting that defaults off (PR 1), then build on top and merge dark. A draft older than 2 h is escalated by the orchestrator; drafts are for work in progress, never for approval."~430Personal ~/.claude/CLAUDE.md › Shippingapplied
2Guard updates the branch itself on a "main moved" hold"When the guard holds for 'main moved since merge base', it runs update-branch itself (once per hour at most). Adding rows to a drift-test inventory or baseline is not a check change."~185Auto-merge guard (Gera's lane)Smith ask
3Deploy-stall alarm"If production is more than 45 min behind a merged main commit, run deploy-stall-check.sh: one bridge post with the cancelled-run ids, and promote-now if the fix is a customer bug."~170New script + personal CLAUDE.mdapplied
4Hard 1,000-line ceiling"review:self fails any PR over 1,000 substantive lines. 600–1,000 lines needs ## Why not split in the body."~70–140Repo PR (self-review script) + repo CLAUDE.mdrepo PR pending
5Sweep every open PR >4h"Any fede-authored PR open more than 4 h that is not armed, merged, or waiting on a named check is swept: the orchestrator messages its owner within 30 min, and the nightly sweep lists every one with owner and cause."~90Personal CLAUDE.md › Always shipping + sweep scriptapplied
6Extend review:self to catchable 🔴sReal-name scan against the gold set and knowledge exports; a Proof block must carry an eval run id whose head SHA equals the PR head; fence and drift tests for touched files run locally under nice instead of being skipped under load.~80Repo PR (self-review) + builder promptrepo PR pending
7No stacks — enforce the existing rule"gh pr create --base other than main is refused unless the base is int/*."~40~/.claude/scripts pre-tool hookrepo PR pending
8One round rule, not three"Rounds are fine while each finds a new defect. Two 🔴 on the same mechanism: stop patching, push one redesign with the reason in the body. A 🔴 saying the last blocker is still open means the push was wrong: fix before the next push. Suggestions (🟡) never earn a round." Resolves the clash between Constitution IV.8, Fede's 09-17 "never split" ruling, and the repo manual.~15Repo CLAUDE.md (replaces "round count is not a stopping condition") + Constitution IV.8applied

Estimates scale the 4-day window by 1.75 for a weekly figure; they overlap, so do not add them up.

Dark-features register (minimal)

Problem: work that merges dark gets forgotten — the first-email engine sat unfinished for days, and several weekend features are dark with no owner tracking the turn-on.

Feature inventory, Sep 15–29 2026 — what's on or off, per company

Fede, 2026-09-29: "we built a lot of stuff in the last two weeks and I don't know if everything's on or off." This fills the register above with real data. Method: grouped ~3,570 merged PRs (2026-09-15 to 2026-09-29) into features, found each feature's setting key in the settings registry (src/lib/domain/portfolio/registry/keys.ts), then read the LIVE value straight from production DynamoDB on 2026-09-29 for the four companies below. No customer names, no phone numbers.

FeatureWhat it doesSetting keyMergedWestern SlopeJP&Co (Camellia + Yale 25)Situs GroupWillows (test)Proof
Email-lead engineReplies to Zillow/listing-site emails by finding or creating the AppFolio guest card and answering from the right mailbox.Organization.settings.leadSource === 'listing_site_email'09-15→09-29OFF (leadSource=appfolio_guest_cards)OFF (unset)OFF (unset)ONno proof link
Guest-card first-email engineClara's first reply on a new guest card walks property → availability → calendar → knowledge. Always runs now — the on/off switch was deleted 09-29 (#13519).leasing.guestCardReplyScope (default system_only)09-18→09-29ON, system-only scopeON, system-only scopeON, system-only scopeON, system-only scopeno proof link
Guest card written from a callA leasing call creates or updates the AppFolio guest card.PropertyLeasingSettings.guestCardWriteFromCalls (property, default off)09-15→09-29ON (all 70 properties)OFF (Camellia, Yale 25)OFFONgo-live tracker
Guest card written from a textSame as above, for a leasing text thread.PropertyLeasingSettings.guestCardWriteFromTexts (property, default off)09-15→09-29OFFOFFOFFONno proof link
Clara texts the application linkClara sends the AppFolio application link herself, in her own thread, instead of AppFolio texting it from its own number.Organization.applicationLinkClaraTexts (default off)09-15→09-29OFFOFFOFFOFFno proof link
Self-guided toursProspect books and enters a home without a person present. Only 1 of 3 planned PRs is merged (#12855); nothing reads the switch yet.Organization.selfGuidedToursEnabled (default off)09-29OFFOFFOFFOFFnot buildable yet — see below
Promise ledger / matters / nagsTracks a spoken promise and pages staff if it's not kept. Retirement is in #13801 (open, armed for auto-merge tonight, 2026-09-29); until it merges, extraction still runs everywhere and only the caller-voice leg and PM nag ladder are off.mixed: caller leg = Property.promiseLedgerCallerOutboundEnabled (default off)09-16→09-29OFFOFFn/a (org not live)OFFno proof link
Staff tour alertsA staff person who opts in gets texted/emailed when a tour is booked, moved or cancelled.per-person opt-in under Settings > My notifications — not a company switch09-17→09-29n/a (per person)n/a (per person)n/a (per person)n/a (per person)no proof link
Coworker back-and-forth (staff texts Clara)Staff can text Clara to find/create a guest card, add a note, or book/move/cancel a showing. Always on since 09-29 — both switches guarding it were removed (#13577, #13613).none (removed 09-29)09-25→09-29ONONONONno proof link
Tour time windowsOffers a morning/afternoon window in the first email or on a call before booking an exact slot.Organization.tourTimeRangesEnabled (default off)09-15→10-01ON (10-01, staff_fede_relay_20261001)ON (10-01, staff_fede_relay_20261001)ON (10-01, staff_fede_relay_20261001)ON (10-01, dry run)Willows real-send proof, #13798
Call-routing V2 (new phone-routing engine)A from-scratch call router, built to test for design flaws without touching the routing every company uses today.call_routing.v2 (org, default off, fails closed)09-15→09-29OFF (old router)OFF (old router)OFF (old router)OFF (old router)no proof link — zero rows exist anywhere, incl. the Willows/Fairhaven POC it was built for
Module switches (Leasing / Maintenance / Renewals / Collections)Every send and write checks whether that module is turned on for the building.enabledModules.* (company + property, absent = on)09-16→09-29Leasing ON; Renewals, Maintenance, Collections OFFLeasing, Renewals, Maintenance ONEverything explicitly OFFLeasing, Renewals, Maintenance ONgo-live tracker
Listings location factsGeocodes each home so Clara can describe nearby streets and landmarks.Organization.locationFactsEnabled (default off)09-29OFFOFFOFFOFFno proof link
Voice fast laneEngineering-only tool: try a voice-agent change on a real call before opening a PR, on a throwaway line.none needed — internal, not customer-facing09-17→09-29n/an/an/an/an/a
Staff can ask Clara to text a prospect from the leasing deskStaff ask Clara to text a prospect; the text_prospect tool writes by id behind the provenance guard. The cut-over merged 2026-10-17 (#20857); the tool is in the live desk’s tool set behind leasing.staffAskedProspectTextEnabled, still OFF everywhere. A platform admin sets it from the customer page, Leasing row “Staff can ask Clara to text a prospect”. Owner lane: coworker-loop redesign (docs/planning/coworker-loop-redesign-2026-10-17.md), session 007. Turn-on: Fede, blocked until these follow-ups are closed: #20664 (language and verbatim text unchecked), #20665 (double send), #20666 (person at several homes: which line), #20726 (Supervised buildings refuse staff-asked texts).leasing.staffAskedProspectTextEnabled (Organization field, default off)10-17 (#20637, merge commit 78f6651925); 0 days dark at mergeOFF (default)OFF (default)OFF (default)OFF (default; Willows and Fairhaven)pending — live Willows/Fairhaven run of the staff-asked text not yet done

Live values read 2026-09-29 by targeted get-item/query calls against propflow-prod (org PROFILE row + one sample property's LEASING_SETTINGS row per company); no full-table scans. Org ids: Western Slope org_c42d9150…, JP&Co org_jpco, Situs Group org_a3376147…, Willows org_sandbox.

Row for #20637 (staff-asked prospect text) added by hand 2026-10-17; it is not part of the 09-29 live read above.

Not merged yet (Fede-authored PRs open >24h, as of 2026-09-29)

#12871 and #12910 armed for auto-merge; #12879 and #12892 stacked behind them, armed as each parent merges. All four are the self-guided-tours chain; the feature above reads OFF everywhere until they land.

No hard WIP cap

Not recommended — the data does not support it. Fede rejected one on 09-19, and the WIP problem is made of the drafts in change 1, not of too many live PRs. Enforcing change 1 plus the existing 3-per-session soft limit is enough. Nor a round cap that merges on 🟡: 🟡 already never holds, and 73% of 🔴 findings in the window were real defects.

Every PR in 09-15..09-28 with 30+ comments (244) — click to expand

Red-cause labels are from a keyword classifier (rough: 56% agreement with a model-read sample). "Machine-written" excludes only the three rows of human/session prose from the comment-source breakdown above.

PRauthorcommentsroundsred roundsred causesrepeat-blocker redsmachine-writtenopen→merge hlines
#9425fede156169defect 6, stale 2, process 1176%13.52684
#9721fede1221913defect 10, proof 1, process 1, reversal 1175%14.53030
#9782fede118223process 2, reversal 1199%19.43624
#8962fede96125defect 5283%15.72237
#9426fede891512defect 7, proof 3, process 2185%8.83994
#12017fede87159proof 8, defect 1478%8.12249
#9633fede8286defect 4, proof 1, infra 1278%3.33114
#9635fede81109defect 5, proof 3, process 1474%4.21337
#10775fede81148defect 8494%24.21490
#9427fede78133process 2, stale 1195%17.6904
#9933fede781310defect 7, stale 2, infra 1291%4.81705
#9105fede78114process 2, defect 2282%12.53608
#9783fede77119defect 8, process 1688%19.51774
#9990gera761513defect 13784%30.48432
#12027fede75116defect 3, proof 2, reversal 1469%5.52354
#9921fede73108defect 5, process 2, proof 1589%4.01973
#9203fede73155defect 5184%7.81370
#9046fede72158defect 6, process 1, reversal 1599%14.91401
#9956fede72129proof 2, ci-red 2, defect 2, stale 2, process 1488%5.08935
#8649fede71138defect 5, proof 2, process 1286%8.91648
#9149gera70103defect 3077%4.31010
#10134fede6897defect 4, process 3194%2.81722
#11865gera67220-088%14.11584
#9994gera661312defect 6, rule 4, proof 2967%29.74000
#12977fede6597defect 6, rule 1588%8.32015
#9497fede64108defect 6, proof 1, stale 1688%6.1683
#9844gera64101rule 1078%3.4979
#8523gera64143defect 2, proof 1189%23.72430
#13187fede631312defect 11, rule 17100%open1344
#12791gera62124defect 3, process 11100%32.51533
#9272fede62126defect 3, reversal 2, ci-red 10100%4.9799
#9484fede6173stale 2, process 1287%10.2294
#9653fede61108defect 8187%6.42603
#9435fede60135defect 2, proof 2, ci-red 1392%12.4411
#12974gera6096defect 4, proof 2087%4.01518
#10131fede59107defect 4, infra 1, proof 1, reversal 1385%4.6304
#9664fede5854defect 3, ci-red 1271%1.33398
#11115fede571610defect 8, process 22100%6.62429
#9705fede5773defect 2, stale 1070%1.9764
#8670fede5765defect 4, proof 1289%10.3848
#10769fede56105defect 1, process 1, reversal 1, proof 1, stale 1193%8.4667
#9928fede5676process 4, defect 2591%1.4804
#9961fede5499defect 7, process 1, rule 1281%closed2404
#9553fede53104defect 3, reversal 1196%6.3251
#10015fede5353defect 2, proof 1283%1.41713
#9234fede5282process 2098%4.51567
#9511fede5295defect 5390%7.5208
#9492fede51107defect 6, process 1286%3.31574
#10309fede50114defect 3, process 1184%6.0954
#9053gera50112stale 1, proof 1088%closed794
#13036fede50118defect 7, reversal 1386%open1449
#11872fede5065defect 52100%5.23144
#9013fede5070-0100%8.4528
#9070gera50130-098%3.3742
#9658fede4997process 7498%3.21398
#12856fede49119defect 6, process 3494%6.43471
#12008gera49116defect 3, proof 2, reversal 1094%4.92293
#8165gera4983? 2, proof 1171%113.31056
#10836gera48117defect 3, rule 2, ci-red 2473%3.32898
#9848gera48111defect 1092%11.51634
#9720fede4864defect 4396%2.23022
#12307fede4898defect 7, proof 1083%50.21330
#8166gera4880-083%90.81102
#9622gera47116defect 4, proof 2179%14.31093
#8978fede4762defect 2098%1.4926
#10049fede4797process 3, defect 2, proof 2198%4.7974
#9081gera4676defect 5, process 1078%34.11667
#10604fede4575defect 5087%6.8994
#10008fede4553process 3393%1.9746
#8986fede4563defect 1, process 1, reversal 1293%13.6683
#7942gera4580-064%97.4791
#8945gera4562process 1, infra 1180%2.01320
#9023fede44101defect 1091%13.6666
#9244fede4494defect 4086%2.1172
#9948fede4453defect 2, process 1189%1.42683
#8977fede4473defect 2, proof 1289%12.0276
#9756gera4462process 2198%2.01858
#9452fede4392ci-red 2091%11.9278
#8893fede4363defect 3086%2.71229
#9039fede43107defect 6, process 13100%14.61072
#8813gera4363? 1, defect 1, process 1079%4.31300
#9750fede4361process 1084%3.4883
#12286fede43107defect 3, process 2, proof 22100%4.61367
#12305fede4363defect 1, proof 1, reversal 11100%6.31471
#9477fede4364process 2, defect 1, proof 1281%4.9118
#11414fede43118process 5, defect 1, infra 1, stale 1698%12.84308
#10201fede4384defect 4088%2.11072
#10241gera4310-0100%5.522
#9402gera42133proof 2, defect 1195%18.71795
#9487fede4275process 3, proof 2390%1.9550
#9576gera4275defect 5395%closed2030
#9509gera4280-098%14.3557
#12863fede4295ci-red 2, defect 2, stale 1083%open1246
#10157fede4280-095%7.21635
#12149fede4262defect 1, ci-red 1181%closed997
#8674fede4263defect 2, stale 1086%8.7827
#11985fede4284proof 2, defect 2276%2.41348
#9543fede4184defect 3, proof 12100%6.5675
#12022fede4154defect 2, proof 2376%1.5964
#12076gera4183defect 3190%5.51295
#8937gera4171proof 1085%2.4736
#9167gera4174defect 4390%6.31149
#9979gera4162defect 1, process 1266%3.4806
#11765fede4173proof 2, defect 1280%3.31594
#11292fede4188defect 6, proof 2495%closed3702
#9860fede4173defect 3288%3.0821
#9771fede4151defect 1183%11.0902
#10835fede4084defect 3, stale 1288%4.8998
#8888fede4043defect 3192%3.1590
#10064fede4083defect 3098%19.21271
#8844fede4062defect 2180%2.3435
#10560fede4093defect 3180%5.793
#10567fede4053defect 2, process 1178%2.61634
#10016fede4043defect 31100%3.91258
#9180gera4090-088%4.81283
#9753gera4071process 1178%7.41444
#10316fede3985defect 4, rule 12100%3.51858
#9455fede3975defect 3, proof 2287%4.4406
#10822fede3986proof 3, process 2, ci-red 1492%21.01936
#10877fede3965defect 4, rule 1154%2.61164
#8782gera3970-082%3.51272
#9779fede3952defect 2185%8.6474
#9885fede3975defect 2, proof 2, ci-red 1382%2.51201
#10534fede3953defect 1, reversal 1, process 1085%1.21094
#9089gera3971process 10100%1.9158
#9109gera3970-0100%4.042
#8669fede3964defect 3, process 1190%closed864
#9304gera3987defect 3, process 2, proof 2477%2.0731
#10397gera3881reversal 1182%3.0743
#10694fede3852defect 1, reversal 1092%3.11696
#9030gera3870-074%17.91374
#8852fede3873stale 1, process 1, proof 1189%2.8862
#10010fede3874defect 4292%13.02479
#10166gera3840-095%3.312
#9737fede3863defect 3079%1.9322
#9282fede3864defect 4189%2.2194
#10904fede3876defect 5, process 1184%2.31341
#8780gera3851defect 1066%4.0446
#8999fede3851defect 1084%1.1556
#9784fede3873defect 2, reversal 1287%8.61534
#10388fede3774defect 3, reversal 1092%2.51296
#10440gera37127proof 3, process 2, defect 2476%8.91286
#9479fede3751process 1078%6.4312
#9962fede3773defect 3095%13.42267
#13185fede3782proof 21100%open1239
#8944gera3762defect 2084%0.8650
#10141fede3776defect 3, proof 2, rule 1089%2.51617
#11901fede37107defect 3, ci-red 3, stale 12100%2.21992
#10882fede3784defect 4292%4.11814
#10418gera3685process 3, defect 2294%91.61563
#8864gera3664defect 2, stale 2178%1.1154
#9062gera3691reversal 1089%14.51091
#9125gera3686defect 5, infra 1492%1.8665
#8309gera3641defect 1086%52.2987
#11790fede36124process 2, ci-red 1, proof 1297%7.8982
#8702gera3631defect 1097%0.6602
#9459fede3662defect 2086%5.4579
#9475fede3665proof 5278%6.2428
#9874fede3664process 3, stale 1192%4.05441
#9308gera3643defect 3075%1.2507
#9758gera36100-094%3.41535
#12679fede3575defect 4, proof 14100%14.72126
#11913fede3576defect 5, rule 11100%3.52153
#9236fede3585defect 4, proof 12100%2.22797
#8796gera3551process 1089%2.5799
#9890fede3542defect 2097%1.8752
#8736gera3571proof 1094%60.32135
#12289fede35106defect 4, process 2274%3.0264
#10025fede3553defect 3297%3.11043
#9884fede3542defect 1, ci-red 1091%8.9641
#7366gera3581defect 1071%172.1762
#9657fede3411defect 1068%0.51582
#10713fede3474rule 2, proof 1, defect 10100%9.71061
#9527gera3481defect 1094%175.22081
#9524fede3463process 2, stale 1097%2.41256
#9057gera3480-0100%2.61410
#10756fede3462process 1, defect 1088%5.92052
#9583fede3452defect 2091%4.7315
#9457fede3473defect 30100%3.7502
#8637fede3472defect 1, stale 1194%29.2845
#11227fede3364stale 2, process 1, defect 1285%11.01808
#9204fede3364process 2, proof 1, defect 1197%1.8556
#9235fede3362defect 2091%closed657
#12855fede3373defect 1, proof 1, ci-red 1197%open1146
#10706fede3364defect 42100%9.81410
#9966fede3342defect 1, proof 1094%6.51451
#10012fede3332defect 20100%3.1841
#9833gera3362process 2085%1.21235
#10752fede3385defect 3, process 22100%1.9404
#10004gera3363defect 1, process 1, reversal 1067%29.21190
#8738gera3330-097%3.2313
#12455fede3398defect 6, proof 2497%2.81102
#9088gera3372defect 2197%1.5730
#9106fede3353process 3282%2.51179
#8572fede3373process 1, infra 1, defect 1179%4.9401
#9201fede3364defect 2, proof 2288%2.41203
#11075fede3264defect 4188%22.6322
#10281fede3274defect 3, infra 11100%4.4913
#9443gera3251process 1084%2.31028
#8855fede3252defect 2088%1.61673
#12038fede3276defect 62100%10.02669
#8853fede3251defect 1181%1.8911
#12643fede3272proof 2088%7.8855
#10167fede3273proof 2, defect 1197%3.21495
#9168gera3252defect 2075%2.81127
#8560fede3272rule 1, ci-red 1172%6.11080
#9891fede3252process 1, defect 1191%2.3699
#10572fede3252defect 2194%1.91169
#10031fede3254defect 40100%2.21405
#11943fede3254defect 4181%1.0795
#9291gera3264defect 4091%1.6798
#9179gera3292defect 1, ci-red 1091%4.6869
#11160fede31117defect 6, proof 1394%10.81139
#9644fede3162defect 2197%1.4630
#9717gera3190-065%41.8885
#9054gera3154process 3, defect 1181%14.7382
#9968fede3164defect 4097%2.1603
#10130fede3164defect 2, reversal 1, ci-red 1194%1.6963
#8900gera3163defect 3287%1.2809
#12536fede3154rule 1, proof 1, defect 1, process 1087%2.3585
#9852gera3181process 1194%10.22320
#11896fede3152defect 2184%1.21276
#10933fede3175defect 4, stale 1294%2.61140
#9579fede3163process 2, defect 11100%3.4191
#11372fede3154process 2, defect 2184%3.5889
#10240fede3141defect 1184%6.7770
#9883fede3153process 2, defect 1290%1.51352
#8964fede3141proof 1087%2.0523
#9226fede3072stale 2097%1.7540
#10255fede3054defect 2, proof 2083%closed972
#10794gera3043defect 3183%3.21161
#13192fede3071defect 11100%2.9564
#13245fede3041defect 10100%open1557
#8925fede3033defect 3083%closed1209
#12595gera30103ci-red 3190%4.21133
#12648fede3042defect 2190%10.4102
#11941fede3073defect 3197%6.51630
#8431gera3073proof 2, defect 1080%33.6538
#12255fede3083defect 3277%3.0283
#12268fede3073defect 2, process 1173%3.2415
#9084gera3065defect 2, proof 2, process 1297%1.0591
#11283fede3055defect 4, ci-red 1493%closed2591
#10253fede3073defect 3090%6.0963
#9285fede3052defect 1, process 11100%1.61182

Retro 2026-09-29 — "finish all WIP tonight" ended with 15 open PRs

Fede's question, verbatim: "every time we say no more work in progress, finish tonight, then I come back and there's dozens of open pull requests and the sessions seem to be asleep. What is going on?" Sessions were awake and working almost the whole night — the accounts show pushes, rebases, and merges into the early morning. The open list built up from four separate mechanisms, not from anyone stopping.

What Fede saw at 07:50 MDT

PRAgeStateOwner sessionCause
#13728~1harmed, green003second review round just closed
#13714~2hblocked003required CI job skipped on head
#13613~7hDIRTY000sibling PR merged same lines
#13611~7hDIRTY000six review rounds, still rebasing
#13600~8h🟡 armed001blocked 6h on a stale review check-run; fresh head pushed
#13583~8hmerged (sweep stale)000landed before the morning list updated
#13577~9hunarmed000six rounds, scratchpad file collision
#13559~10hgreen, unarmed000waiting on the re-arm sweep
#13534~10hgreen, unarmed000waiting on the re-arm sweep
#13511~10harmed001eleven review rounds, redesign cycles
#12855~37hblocked on Fede003over the 1,000-line gate, needs admin merge
#12871stackeddraft, blocked003waits on #12855
#12879stackeddraft, blocked003waits on #12871
#12892stackeddraft, blocked003waits on #12879
#12910stackeddraft, blocked003waits on #12855

Ages are hours-open readings from the PR-timeline pull (collection window ends 14:30Z), so a few merged shortly after Fede's 07:50 look. The chain PRs predate the pull's window; their age is described, not measured.

Root causes, ranked by hours lost

  1. A turn ended "waiting on PR events" while a DIRTY or disarmed PR fires no webhook. Biggest single loss. Session 000 went idle 166 minutes (00:04–02:50Z) and 293 minutes (09:08–14:01Z) — almost 7.7 hours — both times with only the event pipe registered while its PRs sat DIRTY or unarmed. Session 001 registered a five-minute GitHub poller that the interval guard killed (exit 144); a red check at ~07:45Z wasn't read until ~13:55Z, a six-hour gap.
  2. Red main cascade. Mechanism: repo-wide ratchet tests, branch protection set to non-strict, an affected-only test path on ordinary PRs, and every red main run disarms every open PR. Overnight reds: #13566 (team_alert), #13601 (drift-row import guards), #13609 (NONE_RATCHET), the #13690→#13704 pair, the #13704→#13717 pair. Session 004 separately logged about 50 main-red runs across 09-28 daytime (10:01–20:27Z).
  3. A pre-push control pins a line number. The "absent-means-provenance" mutation control asserts the settings resolver at a fixed line. Every merged setting moves that line, and then every push in the repo is refused until someone re-pins it: twice on 2026-09-29 (#13602 moved it, #13640 re-pinned 08:30–09:00Z; another setting moved it 107→110 at ~14:40Z, Gera's #13754 derives the line from the key list instead). Session 000's #13577 sat ready but unpushable in between. Same family as the ratchets above: a rule keyed to a line number or a count breaks on every unrelated merge.
  4. Green PRs sat BLOCKED on stale check-runs. Two shapes, ~8 hours between them. #13600 (session 001) was armed and green for six hours while GitHub kept a carried-forward review=failure check-run from 07:45Z; a later review=success in a different check suite never overrode it, and a review rerun re-stamped success without clearing it. #13714 (session 003): a PR whose affected-test selection exceeded the 200-file cap gets "Unit Tests (decision)" left in progress until review, the required "Unit Tests" verdict posts only on a review run, and the review bot skipped the new head because GitHub had moved an earlier APPROVE forward. Cures today: gh pr update-branch for a fresh head, and dismissing the stale bot approval. Both are handed to Smith on the bridge; the fix is to compare against the original commit when deciding whether a review still stands, and to post one required verdict per head.
  5. The load guard refused local test runs. Spotlight's mds/mdworker processes sat at 200–350% CPU indexing 16 full lane clones (not worktrees), so the guard blocked builders' own test commands and CI became the first run of the tests. Each defect that a two-minute local run would have caught instead cost a full CI-plus-review round: ~1.5h for session 001, ~2h for 006, two refusals logged by 000.
  6. Review rounds ran long. #13511 eleven rounds (fixture drift, a judge-error control that exited 0, a too-loose rubric, a leaked base-snapshot street, guard-pin dodges, main's ratchet moving mid-flight); #13629 eleven-plus (two 🔴 on the same release path, redesigned); #13611, #13577, #13548 six rounds each (docblocks plus an unsourced count; a scratchpad file two PRs both wrote; green then DIRTY).
  7. Stacked PRs got auto-closed. Arming a parent with --delete-branch auto-closed its stacked child twice — #13448 and #13635 — and a closed PR can't retarget, so each needed a brand-new PR (#13701, #13728) and a fresh review from round one. #13614/#13615 were dropped the same way.
  8. The #12855 chain sat on the 1,000-line gate. 37 hours waiting on an admin merge, with three PRs (#12871, #12879, #12892, #12910) stacked behind it and unable to move until it does.
  9. The orchestrator swept only its own PRs. It never ran gh pr list --author fede-propflow, so eight PRs opened by other sessions aged 6–10 hours unseen. It reported "everything merged except two" at 13:5xZ — wrong, for that reason.
  10. CI ran on the expensive path for a week. CI_RUNS_ON sat at 0 for seven days (09-21–09-28), pushing every run onto GitHub-hosted runners instead of self-hosted; session 004's billing read shows $533 / $260 / $1,026 gross on 09-26/27/28.

What changed already this morning

Proposals

ProposalFixes cause #OwnerRecommendation
(a) Require an up-to-date branch, or a merge queue2Fede yes → Smith-CIAdopt. Repo-wide CI behavior change; needs Fede's go before Smith builds it.
(b) Ratchet / fence / one-seam tests run on every PR, never affected-only2, 3Fede yes → Smith-CIAdopt. Same reasoning as (a) — it's the affected-only path that let red main hide until merge.
(c) Red main does not disarm PRs already green at their head2Smith-CIAdopt now. Already the 09-17 ruling; the guard just doesn't implement it yet.
(d) Owning an unmerged PR ⇒ 20-min REST fallback wake, never "waiting on events" alone; Stop hook refuses to end a turn without a registered timer1local hookAdopt now. No customer effect, closes the single biggest loss.
(e) Orchestrator sweeps gh pr list --author fede-propflow every 20 min7local hook / orchestrator ruleAdopt now.
(f) Lanes as worktrees, not clones; Spotlight exclusions applied with sudo3Fede yes (sudo)Adopt the worktree change now. The sudo bash ~/spotlight-exclude.sh step needs Fede's go — today mdfind -onlyin ~/agents still returns 10,624 package.json hits, so ~/agents is not excluded yet.
(g) Refuse gh pr merge --delete-branch on a parent when another open PR bases off it5local hookAdopt now.
(h) Same-file PR families go on one integration branch2, 4repo ruleAdopt now.
(i) A PR over 1,000 lines is cut, not admin-merged6repo rule (gate already merged as #13432)Hold the line: no more admin-bypass exceptions like #12855; split the chain instead of asking for another one.
(j) CI_RUNS_ON back on self-hosted; alert when it flips to 08Fede yes → Smith-CIAdopt. Infra/spend call — needs Fede's go.

Numbers

Source: PR-timeline pull, 2026-09-28T18:00Z–2026-09-29T14:30Z, 100 fede-authored PRs.

MetricValue
PRs opened in window100
Merged / open / closed unmerged86 / 8 / 6
Avg hours open (merged + closed only)2.77
Review rounds, total (39 PRs with reviews)146
Red main runs in window11
Merges to main, all authors (Fede / Gera)333 total — Gera 229 (69%) / Fede 104 (31%)
Rule-change (CI/chore) PRs merged9
PropFlow Docs