Closed general

P0: janitor killed a live browser mid-annotation after a false no-open-windows verdict — destructive kills must verify against the OS window enum

John Lauer · 3mo ago ·closed by John Lauer

P0 (2026-08-10, John hit it live): pup's janitor KILLED the browser hosting John's open, actively-used windows while he was drawing an annotation. Log sequence this boot:

  • [reconcile] reaped closed session "ab-wiki" (no open windows, periodic)
  • [reconcile] reaped closed session "ai-adom-lbr-wiki-page-in-pup" (no open windows, periodic)
  • [janitor] reaped 11 orphaned pup browser process(es) that no live session owned — 0 live profile(s) were spared

Both sessions had OPEN, VISIBLE windows (John was mid-annotation in adom-lbr). The reconciler's "no open windows" verdict was FALSE, the sessions were reaped, and the janitor then saw the (live) Chrome processes as unowned and taskkilled them. User-visible result: window vanished from the taskbar mid-draw, annotation lost, no capture, no Crashpad dump (it was a kill, not a crash) — initially misdiagnosed as a Chrome crash until the dump census showed none.

Context that likely fed the false verdict: minutes earlier the session had heavy churn (ghost-window adoption + takeover closes + a full profile cycle + repeated bridge respawns). The reconciler's open-window detection must be treated as UNRELIABLE input: a destructive kill needs GROUND TRUTH (enumerate the process's actual OS windows via desktop_enum_windows and refuse to kill any process that owns a visible top-level window), the same bare-over-wrong doctrine as overlays and the #434 refCount lesson: never let a derived model authorize destruction.

Fix sketch: (1) janitor: before killing any "orphaned" chrome pid, enum its visible Chrome_WidgetWin_1 windows; any visible window = NOT orphaned, adopt instead of kill. (2) reconcile: "no open windows, periodic" must cross-check the OS enum before reaping a session. (3) ralph: a churn harness (adopt + takeover-close + respawn cycles) asserting no live-window process is ever killed.

2 Replies

John Lauer · 3mo ago

Shipped in pup 2.0.130. The doctrine from this incident is now enforced in code: no derived model authorizes destruction, the OS is asked first.

  1. Janitor (reapOrphanPupChromes): before killing any "orphaned" pid, pup asks the OS which candidate pids own a visible top-level window (Get-Process MainWindowHandle != 0). Any pid that does is by definition NOT an orphan and is spared, along with every sibling process sharing its user-data-dir (so a spared parent's renderers are never killed out from under it). Spared windows are left for pup_rescan / the health check to re-adopt, and the sparing is logged loudly plus surfaced via the cleanup report. The probe fails SAFE: on error it returns an empty set, which makes the pass exactly as permissive as the old code, never more destructive.

  2. Reconciler: the "no open windows" CDP verdict is now cross-checked against ground truth before reaping. If the session's stored hwnd (or a title lookup) resolves to a window that user32 IsWindow + IsWindowVisible confirms alive, the session is SPARED, logged, and a rescan re-attach is kicked off instead. Ground truth failing to find a window falls through to today's reap, so genuinely closed sessions still get cleaned.

  3. The churn ralph harness (adopt + takeover-close + respawn cycles asserting no live-window process is ever killed) is not yet automated; the guards above are the load-bearing fix. Verified live: the new build's periodic sweeps run clean with live windows present.

John Lauer closed this · 3mo ago
John Lauer · 3mo ago

Follow-up: the churn ralph harness asked for in the fix sketch is now in the repo (tools/churn-ralph.sh) and passes live.

It opens 3 real background windows, records each one's OS hwnd, then drives three rounds of the exact churn that caused #46 — pup_rescan (adoption), pup_quit_idle_browsers (the verb that killed the live-window browser), a takeover-close + immediate reopen (the adopt/close/respawn race) — and after every round asserts each UNTOUCHED window is still alive AND visible on the desktop. Ground truth is deliberately independent of pup's session model: pup_window_info hands back the OS hwnd, then ab's desktop_screenshot_window captures that hwnd directly; if the browser had been killed, the hwnd is gone and the capture fails (exactly the user-visible failure). A vanished live window fails the run.

Live result against a real desktop on 2.0.141: 12/12 PASS, GREEN — "no live window was ever killed under churn."

Run it with: tools/churn-ralph.sh [target]. Keeping #46 closed; reopen only if this harness ever goes RED.

Log in to reply.