macOS XProtect definition updates can start SIGKILLing the exact Chromium
revision the lockfile pins (xprotectd killed revision 1208's headless shell
at spawn; the failure surfaced as a generic launch timeout). New
browse/src/xprotect-heal.ts heals it, once per process:
- Classifier (F9): positive signatures sourced from the #2554 report +
Playwright's launch-error format (signal=SIGKILL process-exit lines, and
launch timeout WITH a <launched> marker), negative-checked FIRST against
missing executable, spawn EACCES/EPERM, Linux sandbox denials, and plain
exitCode=1 crashes. darwin-gated.
- Heal (F4 one-shot, in-memory flag): clears com.apple.quarantine via
`xattr -dr` on chromium* revision dirs in the Playwright cache ONLY —
never a GSTACK_CHROMIUM_PATH bundle (probePoisonedChromiumBundle's scope
contract, double-gated at the call sites via usesCustomExecutable).
- Reinstall (E1/ENG-OV3): `bunx playwright install --force chromium` run
FROM THE GSTACK INSTALL ROOT — the root whose
node_modules/playwright-core/browsers.json pins the SAME chromium
revision our embedded playwright-core expects (a cwd-resolved bunx would
fetch latest and heal to the wrong revision). Bounded at 120s with a
process-GROUP SIGKILL on timeout; on any heal failure the caller gets the
ORIGINAL launch error + manual `bunx playwright install chromium`
guidance — the CLI never hangs.
- Verification (F9): post-install asserts the REGISTRY-derived executable
path exists (the revision dir playwright-core 1.62.1 expects), not merely
install exit 0.
- Logging (F11): every action emits one structured stderr line
([browse:xprotect-heal] JSON).
All three launch sites in browser-manager.ts (headless launch, headed
launchPersistentContext, handoff relaunch) route through
launchWithXProtectHeal with one post-heal retry. setup's
ensure_playwright_browser failure path gains the same quarantine-clear
(_clear_playwright_quarantine, Darwin-only, Playwright cache scope) before
its Chromium reinstall.
Tests: browse/test/xprotect-heal.test.ts — 33 pass (classifier both
polarities, one-shot guard incl. failed-heal consumption, custom-executable
scope, registry-revision expectation vs playwright-core browsers.json,
install-root revision matching, quarantine-clear scope, wrapper retry +
guidance surfacing). browser-manager unit/custom-chromium: 36 pass.
bridge-chromium-e2e real-launch smoke: 3 pass. setup-windows-fallback
ln-invariant: 9 pass. bash -n setup: clean.
Fixes#2554.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>