Context
After a headed Playwright run, macOS displayed “Google Chrome quit unexpectedly.” The initial question was whether Neo closes its custom Chrome windows too aggressively.
To assert where the failure occurs, I inspected the matching macOS diagnostic receipt rather than inferring from when the dialog became visible. The latest Google Chrome process launched at 2026-07-30 00:59:33.6584 +0200 and entered the crash reporter at 00:59:33.7120 — roughly 54 ms after launch. Its parent was Node, the process terminated itself with EXC_CRASH / SIGABRT, and the faulting main-thread stack is:
abort
___RegisterApplication_block_invoke
_RegisterApplication
TransformProcessType
ChromeMain
This is not an isolated receipt. The same seat contains 13 Google Chrome application-registration aborts between 2026-07-25 and 2026-07-30; 11 name Node as the direct parent. Most abort within 200 ms of launch. That falsifies “unclean close” as the explanation for this dialog family: the affected process dies during macOS application registration, before a Playwright browser transport can be established. It does not yet prove whether the trigger is rapid same-bundle relaunch, concurrent harness launches, the current two-project handoff, or a Chrome/macOS defect.
Current dev at be17257b3f gives every E2E run two branded-Chrome project lifecycles:
gl-probe project (channel: chrome)
→ close
→ chromium project (channel: chrome)
The presenting default makes no GPU claim, yet still launches the gl-probe browser so that the probe can report “nothing demanded.” Local Playwright 1.61.1 source already attempts Browser.close when a transport exists and only force-kills when no transport exists or graceful close times out. Playwright's public API likewise recommends closing explicitly created contexts before the browser: https://playwright.dev/docs/api/class-browser#browser-close.
Live latest-open sweep: checked the 20 newest open issues and the latest 30 A2A messages at 2026-07-30 01:13 CEST; no equivalent ticket or in-flight claim exists. Existing #15664 / #15813 cover GPU-process death (SIGTRAP), not this AppKit _RegisterApplication SIGABRT family.
The Problem
Intermittent branded-Chrome startup aborts leak macOS crash dialogs onto the operator's desktop. The timing is misleading: the dialog can surface when a test ends even though the recorded crash happened at a browser launch boundary.
The E2E result currently carries no retained lifecycle receipt that identifies:
- which Playwright project/browser owner failed;
- whether a prior same-bundle owner had fully exited;
- whether another harness launched Chrome concurrently;
- whether Playwright had a transport and used graceful close or fell back to force-kill.
A surviving project/browser can therefore continue producing test output while a sibling launch has already polluted the physical desktop. Repeated visual work across agents amplifies the same-bundle launch pressure.
The Architectural Reality
test/playwright/playwright.config.e2e.mjs owns the gl-probe → chromium dependency topology and selects local Google Chrome through channel: 'chrome'.
test/playwright/e2e/utils/gpuIntent.mjs decides whether the current profile actually claims accelerated GL.
test/playwright/e2e/gl.setup.mjs is an effect probe, but its setup-project placement necessarily creates a separate Playwright browser lifecycle.
- Playwright Test owns its runner browser/context fixtures. Neo should not add process-name cleanup around them.
- Any library-owned/custom browser launcher must close its explicit
BrowserContext instances before Browser.close; forced termination is an escalation path, not the ordinary terminal.
- macOS application registration is outside Neo's product runtime. The E2E harness owns launch topology, evidence, and collision avoidance — not AppKit internals.
The Fix
- Add a bounded macOS lifecycle falsifier using the current E2E launch arguments and a minimal page. It must distinguish three cells: one branded-Chrome launch, the current
gl-probe → chromium handoff, and two concurrent same-bundle harness launches. Record process birth/exit order and crash-report deltas without reading personal browser content.
- Bind the repair to the first red cell:
- if the intra-run handoff is red, eliminate the redundant branded-Chrome lifecycle (the presenting profile must not launch a no-op GL probe; an engine-profile effect probe must observe the browser that supplies the measured run or wait for proven native-owner retirement);
- if only concurrent launches are red, add same-bundle launch admission or use a distinct Playwright-owned browser bundle for the probe/run, preserving the measured rendering contract;
- if neither harness cell reproduces, retain the receipt and fail loud on the next observed early browser exit rather than guessing.
- Make early browser exit a first-class test failure containing project, launch profile, exit code/signal, transport-established state, and the bounded macOS crash signature when available.
- For every Neo-owned custom launcher touched by the chosen repair, close explicit contexts before the browser and await the graceful terminal. Disclose any timeout-driven forced kill.
- Preserve the operator's existing Chrome processes exactly; ownership is launch-receipt based, never process-name based.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
| Ordinary headed E2E launch |
playwright.config.e2e.mjs + selected launch args |
No redundant no-op branded-Chrome lifecycle; one presenting owner unless a measured requirement says otherwise |
Fail before product assertions when lifecycle is indeterminate |
Config JSDoc/comments |
Process-order receipt + repeated headed witness |
| Engine GL effect probe |
gl.setup.mjs + gpuIntent.mjs |
Observe the same measured launch contract without an unsafe same-bundle handoff |
Refuse benchmark attribution if GL cannot be observed |
Existing probe output |
Red/green lifecycle matrix + live GL receipt |
| Browser terminal |
Playwright-owned fixture or explicit custom launcher owner |
Explicit contexts settle, then graceful browser terminal; forced kill only after bounded timeout |
Fail loud with terminal receipt |
Owner JSDoc |
Unit falsifiers + macOS lifecycle receipt |
| Operator Chrome processes |
Pre-run native owner set |
Byte-for-byte owner-set preservation outside the exact launched test owners |
Abort cleanup on ambiguous ownership |
Test guidance |
Before/after owner-set diff |
Decision Record impact
none.
Acceptance Criteria
Out of Scope
- Product-side docking behavior.
- Reversing the presenting default delivered by
#16138.
- Suppressing macOS crash dialogs or deleting diagnostic reports.
- Generic desktop/browser process management.
- Reopening resolved GPU-crash tickets.
Avoided Traps
- Do not treat when the dialog appears as when the process crashed.
- Do not “fix” this by disabling Crashpad/crash reporting.
- Do not kill all Google Chrome processes at test end.
- Do not add an arbitrary delay without a red/green retirement measurement.
- Do not conflate this
SIGABRT application-registration family with the earlier GPU SIGTRAP family.
Related
- #16128 / PR #16138 — presenting E2E default.
- #16150 — falsified universal frame-rate-override premise.
- #15664 / #15813 — distinct GPU-process crash family and live GL probe.
Origin Session ID: 019fac4d-7844-7422-9486-7f73ccf308f5
Retrieval Hint: "macOS Google Chrome RegisterApplication TransformProcessType SIGABRT Playwright lifecycle"
Context
After a headed Playwright run, macOS displayed “Google Chrome quit unexpectedly.” The initial question was whether Neo closes its custom Chrome windows too aggressively.
To assert where the failure occurs, I inspected the matching macOS diagnostic receipt rather than inferring from when the dialog became visible. The latest Google Chrome process launched at 2026-07-30 00:59:33.6584 +0200 and entered the crash reporter at 00:59:33.7120 — roughly 54 ms after launch. Its parent was Node, the process terminated itself with
EXC_CRASH / SIGABRT, and the faulting main-thread stack is:This is not an isolated receipt. The same seat contains 13 Google Chrome application-registration aborts between 2026-07-25 and 2026-07-30; 11 name Node as the direct parent. Most abort within 200 ms of launch. That falsifies “unclean close” as the explanation for this dialog family: the affected process dies during macOS application registration, before a Playwright browser transport can be established. It does not yet prove whether the trigger is rapid same-bundle relaunch, concurrent harness launches, the current two-project handoff, or a Chrome/macOS defect.
Current
devatbe17257b3fgives every E2E run two branded-Chrome project lifecycles:The presenting default makes no GPU claim, yet still launches the
gl-probebrowser so that the probe can report “nothing demanded.” Local Playwright 1.61.1 source already attemptsBrowser.closewhen a transport exists and only force-kills when no transport exists or graceful close times out. Playwright's public API likewise recommends closing explicitly created contexts before the browser: https://playwright.dev/docs/api/class-browser#browser-close.Live latest-open sweep: checked the 20 newest open issues and the latest 30 A2A messages at 2026-07-30 01:13 CEST; no equivalent ticket or in-flight claim exists. Existing
#15664/#15813cover GPU-process death (SIGTRAP), not this AppKit_RegisterApplicationSIGABRTfamily.The Problem
Intermittent branded-Chrome startup aborts leak macOS crash dialogs onto the operator's desktop. The timing is misleading: the dialog can surface when a test ends even though the recorded crash happened at a browser launch boundary.
The E2E result currently carries no retained lifecycle receipt that identifies:
A surviving project/browser can therefore continue producing test output while a sibling launch has already polluted the physical desktop. Repeated visual work across agents amplifies the same-bundle launch pressure.
The Architectural Reality
test/playwright/playwright.config.e2e.mjsowns thegl-probe → chromiumdependency topology and selects local Google Chrome throughchannel: 'chrome'.test/playwright/e2e/utils/gpuIntent.mjsdecides whether the current profile actually claims accelerated GL.test/playwright/e2e/gl.setup.mjsis an effect probe, but its setup-project placement necessarily creates a separate Playwright browser lifecycle.BrowserContextinstances beforeBrowser.close; forced termination is an escalation path, not the ordinary terminal.The Fix
gl-probe → chromiumhandoff, and two concurrent same-bundle harness launches. Record process birth/exit order and crash-report deltas without reading personal browser content.Contract Ledger
playwright.config.e2e.mjs+ selected launch argsgl.setup.mjs+gpuIntent.mjsDecision Record impact
none.
Acceptance Criteria
_RegisterApplication/TransformProcessTypeSIGABRTand attributes it to exactly one lifecycle cell.killall,pkill, process-name kill, or title/URL inspection is introduced.Out of Scope
#16138.Avoided Traps
SIGABRTapplication-registration family with the earlier GPUSIGTRAPfamily.Related
Origin Session ID: 019fac4d-7844-7422-9486-7f73ccf308f5
Retrieval Hint: "macOS Google Chrome RegisterApplication TransformProcessType SIGABRT Playwright lifecycle"