Proposed — pending review 2026-08-12 · written by Claude for Fede · companion to the Gmail-duplicate incident brief
Why finished PRs sit open overnight instead of being driven to merged
Case study: the three PRs from the Gmail-duplicate incident (the identity fix, the Willows harness, the scan crash fix). All three reached "effectively done" the evening of Aug 11. Only the identity fix merged that night — and only because Fede asked "why is that still open." The other two sat open until Fede checked in the next morning. This document reconstructs exactly where each one stalled and names the systemic causes, with fix options.
Timeline of the stalls (all times CT, Aug 11–12)
| When | Event | Stall? |
|---|---|---|
| ~2:00p | Identity-fix PR opened; CI + review rounds begin | — |
| 2:00–7:20p | Six review rounds; reviewer correctly blocks 3 times; each fix round needs a manual re-dispatch of the review | Slow but legitimate — the blocks were real bugs |
| ~5:30p | Auth files enter the diff → hard-floor consent needed from Fede | Stall 1: waited silently for consent instead of paging |
| ~6:33p | Behavior-eval sweep fails (pre-existing breakage + grader noise, unrelated to the diff) | Stall 2: unrelated red check has no owner; would have parked the PR indefinitely |
| 7:27p | Identity fix merges — after Fede's "just wrap up the bug" and two explicit approvals | Human push required |
| ~7–8p | Harness PR and scan-fix PR opened; CI goes fully green on both | — |
| ~6:53p / 7:40p | Bot reviews post 🟡 "changes suggested" on both | Stall 3: 🟡 blocks auto-merge but notifies no one and assigns no owner |
| overnight | Nothing happens. Both PRs are one small action from merging. | Stall 4: the driving session went idle; no wakeup was armed |
| 8:15a | Fede: "why are prs still open" → both driven to merged/merging within 40 minutes | Human push required, again |
The tell: once poked, both PRs took under an hour of actual work. The work wasn't hard — it was unowned.
Systemic causes
The auto-merge workflow arms only on 🟢/approve. A 🟡 verdict holds the merge — correctly — but nothing routes the suggestion to anyone. No assignee, no Slack ping, no re-review trigger when the suggestion is addressed. A PR that is 95% done looks exactly like a PR nobody has to think about. Both overnight PRs were in precisely this state, and each suggestion took ~20 minutes to satisfy.
I ended multiple turns with "I'll report when CI settles" backed by background monitors — which only live as long as the session is active. Fede's standing rule for me is explicit: every turn ends with actions in flight AND a wakeup armed. I armed no wakeup and no scheduled babysitter, so when the conversation went quiet, the driving stopped. The "oh yeah, almost done, let me continue" pattern Fede describes is exactly this: the work state survived, the driver didn't.
Two legitimate human gates (the auth-file hard floor; merging over an unrelated red check) each stopped the line. Correct gates — but the failure mode is that a blocked PR just sits. Nothing pushed a notification saying "one yes needed to ship." Fede found out by asking.
Each fix round required someone to trigger the re-review; one re-dispatch cancelled the round already running, wasting a cycle and leaving a stale "changes requested" verdict visible for an hour. Stale verdicts also mislead anyone (human or agent) who glances at PR state to decide if action is needed.
Discovered during this incident: the behavior-eval check usually skips (1-run/day budget) while still reporting green — so "carry the green eval receipt" is being satisfied by runs that evaluated nothing. And when it does run, pre-existing breakage (a template placeholder broken on main since Aug 6) reds the check on whoever happens to trigger it, with no owner for the unrelated failure. Both directions stall or hollow out the pipeline.
Fix options — pick a bundle
Recommended · A — make "done but unowned" impossible (3 small changes)
1. PR babysitter routine: a scheduled agent (every 30–60 min, off-hours included) that lists open PRs authored by Fede/Claude, and for each: satisfies 🟡 suggestions or replies and re-requests review; re-dispatches stale reviews; pages Fede in #updates-fede ONLY when a human gate is the blocker ("one yes needed: auth diff on #5692"). Claude Code's scheduled-routine support does this today — no repo changes needed to start.
2. Auto-merge treats an addressed 🟡 as re-reviewable: when a commit or reply lands after a 🟡, auto-request the re-review instead of waiting for a human to remember.
3. Staleness alarm: any PR green+reviewed but unmerged for >2 hours posts one line to #alerts with the single blocking reason.
B — session-discipline only (no infra)
Claude always arms a wakeup/cron when ending a turn with any PR open, per the existing standing rule — enforced by habit and memory. Cheapest, but it failed this time precisely because it depends on the driver remembering; option A makes the system remember.
C — A plus eval-gate repair
Everything in A, plus: the skipped eval run publishes a neutral check (not success) so hollow receipts are visible; and the Aug-6 vendor-PO placeholder bug gets fixed so the next real sweep can actually pass. Closes the "green means nothing ran" hole the incident exposed.
Resolution — what was actually chosen (2026-08-12)
Fede's decision: none of the bundles as written. No scheduled checkers, no Slack pages, no human-facing alerts of any kind — the driving layer is AI-to-AI only. Machines notify Claude sessions; sessions act or repair; a human hears about it conversationally, never via a page.
A real-time GitHub → local-Claude event pipe was built Aug 9 (GitHub's own event forwarder → a local receiver → hooks that wake any Claude session the moment a review verdict, bot comment, or failed check lands). It ran verified for ~2 days, then crashed Aug 10 at 11:49 behind a stale webhook registration and restarted-and-died ~16,000 times with nothing watching it. Every stall in this postmortem happened after it died. The dead-watcher pattern (cause 2) was not missing discipline — it was this pipe being down.
~90 hours of finished work sat unowned, and the failure is bimodal: with no session awake, done PRs freeze (unpaged human gates ~62h; the soft "changes suggested" dead-end ~28h). With a session awake, merges outrun review integrity (one PR merged 5 seconds after a fresh warning flagging a regression in its own subject; another merged mid-review, its 4 findings landing on an already-merged PR). Same root: no state in the pipeline speaks up or updates itself. Separately, 128 of 130 behavior-eval runs over 48h were green receipts that evaluated nothing, and the one real run failed on pre-existing breakage and merged anyway.
(1) The stale registration blocking reconnection was deleted, and the forwarder now auto-deletes any stale forwarder registration on every start, so this exact death can't recur. (2) The two waiter processes wedged since Aug 10 were cleared, and the waiter lock is now pid-owned so a dead waiter is taken over instead of blocking new ones for days. (3) A health watch inside the waiter re-checks the pipe every 5 minutes; a dead pipe wakes the session with repair instructions — the session fixes it, no human is notified. (4) Verified end-to-end: GitHub delivery 200 OK, synthetic event through the receiver, session-wake exit confirmed.
Option A's babysitter and every paging/alarm element of A and C are rejected per the AI-to-AI-only decision. Still open: the eval-gate repair (skipped runs should publish a neutral not-run check at the run level too, plus the Aug-6 vendor-PO template fix so real sweeps can pass), and the second, Slack-thread event pipe through Agent Smith, whose listener has been missing from disk since ~Jul 9.
Handoff state (as of this doc)
- Identity fix — merged, deployed, prod-proven by the live Willows harness (12/12). Data conversion applied: 44/44 keys resolved; 4 flagged duplicate-people collisions await reviewed merges (includes the
fede@propflowai.coandjosegerag@gmail.compairs). - Willows harness — merged. Known gap: its cleanup sweeps prospect rows but not uniqueness-marker rows (two leftovers were removed by hand); one-line follow-up.
- Scan crash fix — merged morning Aug 12 (by hand, after sitting green behind a stale verdict — the live specimen of causes 1 and 4).
- Unrelated open items — resident break-in callback (apt 405, from Aug 9), the unanswered transfer line, the 20 backfill-duplicate people, the "Unknown"-sender escalation fix, the eval-gate hollowness: all in the Gmail-incident brief with options.