Skip to content

fix(browser): serve waits for chrome, keeps its stderr, and reaps its own Xvfb (DIVE-4400) - #67

Merged
lodar merged 2 commits into
mainfrom
dive-4400-serve-liveness
Sep 18, 2026
Merged

lodar merged 2 commits into
mainfrom
dive-4400-serve-liveness

Conversation

@5dive-bot

Copy link
Copy Markdown
Collaborator

DIVE-4400 arm 1 — serve must not print success until Chrome is alive

Measured on our canary (exact-swallow, 2026-09-13 04:0x–04:2xZ):
5dive browser serve linkedin.com printed
serving linkedin.com on :375 (persistent …) and left no Chrome process at
all
— only the Xvfb it started. browser ls listed the site, the stack status
read state=ok / store=ready, and the only surface that told the truth was a
customer pressing Connect on the live dashboard and getting a 502.

bin/browser backgrounded Chrome with >/dev/null 2>&1 &, wrote
chrome_pid=$! unconditionally, printed success, and judged liveness by the
Xvfb pid. A Chrome that dies in the first millisecond was indistinguishable
from a healthy one, and its stderr was discarded before anyone could read it.

This PR

  • serve waits for the pid, kill -0s it after a beat, and on failure exits
    non-zero with Chrome's own stderr (no longer sent to /dev/null).
  • A failed serve reaps the Xvfb it started, so it no longer leaves an
    orphan display that pushes the next _display_free probe to :376, :377, …
    (two such orphans were standing on the canary).
  • The pidfile is deleted on a failed start, so no later caller reads it as live.

Evidence

tests/browser_plugin_unit.sh208 passed / 0 failed (199 before, +9 arms).

Mutants, anchored to individual arms:

  • drop the Xvfb reap → fails exactly T10u …and REAPS THE Xvfb it started
  • restore the old print-and-return shape → fails 6 T10u arms (rc, the success
    line, both stderr lines, the pidfile, the reap)

Controls, because a negative arm that cannot fail is arm-counting: T10u first
runs a Chrome that stays up on the same rig and asserts serve succeeds and
its Xvfb is left running. The fake Xvfb records its own pid (exec keeps
it) — "no display was left behind" cannot be read off the product's pidfile,
because a correct failure deletes that file.

Not claimed

Not re-measured on the canary: that box's Chrome is still broken (a libc6
upgrade pending reboot — the trigger, not the defect) and this host has no
browser stack installed. The on-box re-measure — serve refusing, the row
reading degraded, Connect still 502 but now visibly — is owed and is the
real acceptance for DIVE-4355.

Arm 2 (the stack health row) is 5dive-ai/5dive-api's companion PR.

🤖 Generated with Claude Code

@lodar

lodar commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Still wanted — this fix never landed and serve on main still prints success with no liveness check. It is not merge-as-is though: plugins/browser/bin/browser has moved 8 times on main since this branched (merge-base 49505cb), so the green here is against a tree that no longer exists. Needs a rebase onto current main and a fresh CI run before merge. Tracked as DIVE-4610.

lodar and others added 2 commits September 18, 2026 05:24
… own Xvfb (DIVE-4400)

`serve` backgrounded chrome with `>/dev/null 2>&1 &`, wrote chrome_pid=$!
unconditionally and printed success. A chrome that trapped in its first
millisecond was therefore indistinguishable from a healthy one to `ls`, to the
stack row and to `viewer` — and the only surface that told the truth was the
customer's Connect press, which 502'd.

- _pid_live: a reaped-pending zombie answers `kill -0` with success, so the one
  moment we need the truth is the one moment kill -0 cannot give it. /proc says Z.
- serve waits, bounded, for the process to still exist before writing a pidfile
  or printing anything; on death it dies E_UNAVAILABLE carrying chrome's own
  stderr (kept in the profile at 0600 instead of discarded).
- a failed serve reaps the Xvfb it started, so it leaves no orphan display for
  _display_free to hop past forever.
- _probe's "UNKNOWN (probe did not load)" now names what chrome said and STAMPS
  the profile, so `ls` can tell it from "never probed".

Tests: tests/browser_plugin_unit.sh T10u — control arm (a chrome that stays up
still serves, its Xvfb left running), then the defect: rc 69, no success line,
chrome's two stderr lines handed back, no pidfile, Xvfb reaped, and the
dashboard's own `viewer` call refusing. 208 passed / 0 failed. Mutants: dropping
the reap fails exactly the orphan arm; restoring the old print-and-return shape
fails six.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…(DIVE-4610)

Rebasing the DIVE-4400 serve-liveness fix onto today's main is textually clean
and semantically NOT: 537 passed / 3 failed against a 531 / 0 control at
origin/main. All three are the same shape -- T15d2, T15e and T16g assert that a
screenshot or a run PUTS THE CUSTOMER'S BROWSER BACK, and their fake chrome
exits the instant it is launched. Before this fix `serve` printed success and
wrote a pidfile for a chrome that was already gone, so those arms were reading
the pidfile of a browser that had died; after it, `serve` correctly refuses, and
the arms were measuring the fake's lifetime rather than the product's restore.

So the rig's fakes now identify a serve launch POSITIVELY -- headed (no
--headless) and sized (--window-size) -- and exec sleep, the same way SRVBIN
already does for the T10 serve arms and the T10u control does for its own. It is
matched positively on purpose: a negative match ("no --headless") catches
`doctor`'s --version and `auth`'s headed window, both foreground, and hangs the
suite forever.

That surfaced a second, older leak. There is no DISPLAY in CI, which is the
normal case on a managed box, so T2e's `auth x` takes the server-mode path and
STARTS A SERVE for profile x. That serve was previously a lie and thus invisible;
with a fake that stays up it is real, and a served profile answers `status` with
"UNKNOWN (served on :N)" by design -- silently converting T4c and T11d, the two
positive controls for the word `authenticated`, into assertions about a leak.
T2e now stops what it started.

540 passed, 0 failed on the rebased tree. Reverting bin/browser to origin/main
under these same tests reds exactly 6 arms, all T10u, and nothing else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lodar
lodar force-pushed the dive-4400-serve-liveness branch from 63c0bb7 to c5381bf Compare September 18, 2026 05:40
@lodar

lodar commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Rebased onto main (merge-base was 49505cb, 8 moves ago) under DIVE-4610 — this PR sat open and green for five days while DIVE-4400 closed pointing at a different repo's PR, so task merge-audit could not see it. The bug is still live at origin/main.

The rebase was textually clean and semantically not, which is why it was not a blind gh pr merge:

tree result
origin/main (control, e9d9e70) 531 passed, 0 failed
rebase, product fix only 537 passed, 3 failed
rebase + rig reconciliation (c5381bf) 540 passed, 0 failed
rebase tests, bin/browser reverted to origin/main 534 passed, 6 failed — all T10u

The 3 failures were one shape: T15d2, T15e, T16g assert that a screenshot or a run puts the customer's browser back, and their fake chrome exits the instant it launches. Before this fix serve printed success and wrote a pidfile for a chrome that was already gone — so those arms were reading the pidfile of a dead browser. After it, serve correctly refuses, and the arms were measuring the fake's lifetime, not the product's restore. _shot_restore's own behaviour is unchanged and still graded by T16h.

The rig now identifies a serve launch positively — headed (no --headless) and sized (--window-size) — and exec sleeps, the same way SRVBIN already does for the T10 serve arms. Positively on purpose: a negative match ("no --headless") catches doctor's --version and auth's headed window, both foreground, and hangs the suite forever (measured).

That exposed a second, older leak: CI has no DISPLAY, the normal case on a managed box, so T2e's auth x takes the server-mode path and starts a serve for profile x. That serve was previously a lie and therefore invisible; now it is real, and a served profile answers status with UNKNOWN (served on :N) by design — silently converting T4c and T11d, the two positive controls for the word authenticated, into assertions about a leak. T2e now stops what it started.

Not merged blind and not forced green: CI runs at c5381bf against today's file.

🤖 Generated with Claude Code

@lodar
lodar merged commit 0676827 into main Sep 18, 2026
2 checks passed
@lodar
lodar deleted the dive-4400-serve-liveness branch September 18, 2026 05:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants