# Browser lifecycle runtime evidence

Client request id: `lead-27224-r1-capture-1`.

Evidence for [#27224](https://git.cafe/pingdotgg/t3code/pull/27224) ([GitHub mirror](https://github.com/pingdotgg/t3code/pull/27224)), branch `fix/issue-27136`.

**No additional product change outside browser cleanup was observed in these fixtures.** This is server-only lifecycle evidence; screenshots, viewports and themes are not cleanup proof. All captures are explicitly **harness**: each side imports its own real `HtmlRender.preview` / `HtmlRender.publish` service, config and NodeServices. No server transport, database, live installation, or browser UI is used.

**Coverage: 10/10 required rows captured; 10/13 total matrix rows (11 paired triggers / 22 runs).** A cannot be verified here; X and B are optional and were not run. [Fixture matrix](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/315ea3336b645810/matrix.md).

Environment: fs-04 / Linux 6.1.102 x86_64; Chrome for Testing headless shell 156.0.8078.70 (SHA-256 a313da592261ff1e342c1e507d315f25b1809006c0508f77ce86151d464d4ccb); checkout-selected Node v26.13.0; strace 6.8; Effect/platform-node 4.0.5; HtmlRender service harness, T3CODE_SERVER_BROWSER_SANDBOX=0; AppArmor disabled (N), LSM capability,selinux.

## How to read the captures

- Each row transcript contains the complete Before and After strace, timestamped harness/browser event logs, metadata, and both process snapshots. No capture is replaced by its interpretation.
- Strace uses `-f -ttt -T -yy -s 220 -e trace=kill,exit_group,clone,clone3,fork,vfork,execve,write`. Pipe writes establish CDP command/response ordering. ISO UTC and monotonic nanoseconds accompany driver/fake milestones.
- Release latency below means the observable release milestone → service return: screenshot reply for P/U; last target-close response for H; fake capture completion for F/T/K/N; disconnect trigger for D/L; navigation failure or readiness-driven interruption for E. This avoids mixing page render time into release timing. Leader-exit timing and actual signal timestamps are in each transcript.
- Descendants are followed through clone/fork traces, including after reparenting. Three-second post-return `ps -eo pid,ppid,pgid,sid,stat,etime,cmd` output is filtered to those exact tracked IDs, including zombies. The observer is a subreaper and immediately reaps its adopted exited descendants; each wait status/timestamp is recorded. It does not signal them before the survivor snapshot.
- U intentionally uses a detached relay that keeps Chrome’s input writer open even when the signal-group wrapper is killed. Otherwise EOF can terminate Chrome and confound this mechanism test. Wrapper, relay and real shell have separate session/PGID records; strace proves T3 signals only the wrapper group. Independent controller SIGKILLs appear only after the Before survivor snapshot. This is a synthetic mechanism substitute, not kernel EPERM proof.
- The same pinned Chrome, HTML, host, relevant driver and fixture are used on both sides of each pair. Every run has a fresh synthetic service state directory. No real data or database was read or seeded.
- Timings are one traced pair per trigger, not a performance benchmark. Full operation durations are included separately.

## What else changed

- P PNG hashes and dimensions are identical; H has identical heights at all nine widths. H attachment UUIDs vary by design. P total operation time: 1128 → 944 ms; H: 1992 → 2022 ms. Process IDs, ports, profiles and scheduling vary.
- Existing spawner group SIGTERM still runs After following a clean browser exit (sometimes ESRCH, sometimes clearing/reaping descendants). D/N nonzero exits can trigger an additional existing on-exit signal. Signal-0 liveness probes are visible in raw traces. These are not signals that ended the already-exited leader.
- D/L retain the same diagnostic error output and existing --version probe. E retains the original navigation error and interrupted Exit. No new protocol/result errors were observed in the selected captures.
- Synthetic fixtures use asynchronous socket readers for fd3; fs blocking reads in discarded pilots could stall Node exit. The final U relay deliberately distinguishes protocol close from pipe EOF.
- Initial launch/relay/reader pilots are retained in the raw archive and excluded from selected proof. `selected-runs.json` in that archive identifies every authoritative pair.

## Captures

### P — real preview success (harness)

[Complete paired transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/817eb64a12c867f9/row-P.txt)

| Before | After | Difference |
| --- | --- | --- |
| [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2)<br>T3 group TERM ends Chrome (143) | [c17d32c8ea](https://github.com/pingdotgg/t3code/commit/c17d32c8eabe2b9a8148ba1df30a53e7968a640c)<br>CDP close ends Chrome (0) | 237.341 ms → 119.000 ms; zero After live survivors |

**Before → After difference:** T3 group TERM ends Chrome (143) → CDP close ends Chrome (0); live survivors 0 → 0; milestone-to-return 237.341 ms → 119.000 ms.

**What else changed:**

- PNG SHA-256 identical; height 160 and 3137 PNG bytes on both sides.
- P total operation: 1128.000 → 944.000 ms; release milestone → leader exit: 192.249 → 79.383 ms.
- No additional product behavior change observed in this fixture.

### H — real publish measurement (harness)

[Complete paired transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/443649ac52e41f4c/row-H.txt)

| Before | After | Difference |
| --- | --- | --- |
| [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2)<br>9 target closes; T3 group TERM ends Chrome (143) | [c17d32c8ea](https://github.com/pingdotgg/t3code/commit/c17d32c8eabe2b9a8148ba1df30a53e7968a640c)<br>9 target closes, then 1 browser close; Chrome exits 0 | 247.975 ms → 122.695 ms; zero After live survivors |

**Before → After difference:** 9 target closes; T3 group TERM ends Chrome (143) → 9 target closes, then 1 browser close; Chrome exits 0; live survivors 0 → 0; milestone-to-return 247.975 ms → 122.695 ms.

**What else changed:**

- All 9 nonempty measured heights identical: 320/375/430 → 304; 520/640/728/860/1000/1144 → 160. Attachment UUIDs differ by design.
- H total operation: 1992.000 → 2022.000 ms; release milestone → leader exit: 193.099 → 76.558 ms.
- No additional product behavior change observed in this fixture.

### U — real browser outside signal group (harness)

[Complete paired transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e77e368d547f6963/row-U.txt)

| Before | After | Difference |
| --- | --- | --- |
| [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2)<br>Wrapper ignores TERM, is killed; detached Chrome stays alive | [c17d32c8ea](https://github.com/pingdotgg/t3code/commit/c17d32c8eabe2b9a8148ba1df30a53e7968a640c)<br>CDP close exits Chrome, relay and wrapper 0 | 2060.871 ms → 131.278 ms; zero After live survivors |

**Before → After difference:** Wrapper ignores TERM, is killed; detached Chrome stays alive → CDP close exits Chrome, relay and wrapper 0; live survivors 8 → 0; milestone-to-return 2060.871 ms → 131.278 ms.

**What else changed:**

- Synthetic detached relay deliberately retains Chrome input across EOF to isolate CDP from pipe-lifetime shutdown. Seven Chrome processes plus one relay survive Before; none After. This is not EPERM or AppArmor proof.
- U total operation: 2899.000 → 1169.000 ms; release milestone → leader exit: survived release → 72.657 ms.
- No additional product behavior change observed in this fixture.

### F — fake ignores TERM, honors close (harness)

[Complete paired transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d04ad44e69d25ce1/row-F.txt)

| Before | After | Difference |
| --- | --- | --- |
| [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2)<br>TERM ignored; T3 KILL ends fake | [c17d32c8ea](https://github.com/pingdotgg/t3code/commit/c17d32c8eabe2b9a8148ba1df30a53e7968a640c)<br>Explicit Browser.close, no session id; exit 0 | 2028.000 ms → 50.000 ms; zero After live survivors |

**Before → After difference:** TERM ignored; T3 KILL ends fake → Explicit Browser.close, no session id; exit 0; live survivors 0 → 0; milestone-to-return 2028.000 ms → 50.000 ms.

**What else changed:**

- Focused PR regression fails Before and passes After. Original test EOF/close ambiguity is retained in its test log; independent runtime fake records these as different events.
- F total operation: 2216.000 → 277.000 ms; release milestone → leader exit: 2017.398 → 41.275 ms.
- No additional product behavior change observed in this fixture.

### T — ignores close, accepts TERM (harness)

[Complete paired transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7d681ac65b013469/row-T.txt)

| Before | After | Difference |
| --- | --- | --- |
| [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2)<br>T3 TERM ends fake (0) | [c17d32c8ea](https://github.com/pingdotgg/t3code/commit/c17d32c8eabe2b9a8148ba1df30a53e7968a640c)<br>Close ignored; ~2 s grace then T3 TERM (0) | 17.000 ms → 2023.000 ms; zero After live survivors |

**Before → After difference:** T3 TERM ends fake (0) → Close ignored; ~2 s grace then T3 TERM (0); live survivors 0 → 0; milestone-to-return 17.000 ms → 2023.000 ms.

**What else changed:**

- No KILL escalation on either final run. Successful preview preserved.
- T total operation: 273.000 → 2222.000 ms; release milestone → leader exit: 11.215 → 2015.82 ms.
- No additional product behavior change observed in this fixture.

### K — ignores close and TERM (harness)

[Complete paired transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/015f66dde2c3a702/row-K.txt)

| Before | After | Difference |
| --- | --- | --- |
| [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2)<br>T3 TERM then KILL; SIGKILL exit | [c17d32c8ea](https://github.com/pingdotgg/t3code/commit/c17d32c8eabe2b9a8148ba1df30a53e7968a640c)<br>Close ignored; ~2 s grace, then TERM and KILL; SIGKILL exit | 2022.000 ms → 4027.000 ms; zero After live survivors |

**Before → After difference:** T3 TERM then KILL; SIGKILL exit → Close ignored; ~2 s grace, then TERM and KILL; SIGKILL exit; live survivors 0 → 0; milestone-to-return 2022.000 ms → 4027.000 ms.

**What else changed:**

- Same signal fallback sequence, with measured extra close grace. Successful preview preserved.
- K total operation: 2253.000 → 4274.000 ms; release milestone → leader exit: 2014.23 → 4018.614 ms.
- No additional product behavior change observed in this fixture.

### D — already disconnected, leader exited (harness)

[Complete paired transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/4f72141566eebf98/row-D.txt)

| Before | After | Difference |
| --- | --- | --- |
| [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2)<br>Leader exits 17; operation fails | [c17d32c8ea](https://github.com/pingdotgg/t3code/commit/c17d32c8eabe2b9a8148ba1df30a53e7968a640c)<br>No close or grace; same exit 17 and failure | 107.000 ms → 122.000 ms; zero After live survivors |

**Before → After difference:** Leader exits 17; operation fails → No close or grace; same exit 17 and failure; live survivors 0 → 0; milestone-to-return 107.000 ms → 122.000 ms.

**What else changed:**

- Identical stderr diagnostic "synthetic disconnected leader exit 17" and exitCode 17. Existing launch diagnosis runs an additional --version subprocess on both sides.
- D total operation: 343.000 → 348.000 ms; release milestone → leader exit: 7.588 → 8.836 ms.
- No additional product behavior change observed in this fixture.

### L — already disconnected, leader alive (harness)

[Complete paired transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/479cfdde1fc73bdf/row-L.txt)

| Before | After | Difference |
| --- | --- | --- |
| [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2)<br>fd4/stderr close, ~1 s diagnostics wait, TERM exits fake 0 | [c17d32c8ea](https://github.com/pingdotgg/t3code/commit/c17d32c8eabe2b9a8148ba1df30a53e7968a640c)<br>No close or extra grace; same diagnostics wait and TERM exit 0 | 1156.000 ms → 1110.000 ms; zero After live survivors |

**Before → After difference:** fd4/stderr close, ~1 s diagnostics wait, TERM exits fake 0 → No close or extra grace; same diagnostics wait and TERM exit 0; live survivors 0 → 0; milestone-to-return 1156.000 ms → 1110.000 ms.

**What else changed:**

- Identical "synthetic live disconnect" error output. The existing ~1 s child-exit diagnostic allowance is included in measured latency; --version diagnostic probe occurs on both sides.
- L total operation: 1368.000 → 1362.000 ms; release milestone → leader exit: 1024.139 → 1015.919 ms.
- No additional product behavior change observed in this fixture.

### E — connected error and deterministic interruption (harness)

[Complete paired transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/0908a289ae2dd7c6/row-E.txt)

| Before | After | Difference |
| --- | --- | --- |
| [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2)<br>T3 TERM exits fake 0 for both triggers | [c17d32c8ea](https://github.com/pingdotgg/t3code/commit/c17d32c8eabe2b9a8148ba1df30a53e7968a640c)<br>CDP close exits fake 0 for both triggers | error: 20.000 ms → 21.000 ms; interrupt: 15.000 ms → 20.000 ms; zero After live survivors |

**Before → After difference:** TERM → CDP close; leader exit 0 → 0 for each trigger; live survivors 0 → 0; error: 20.000 ms → 21.000 ms; interrupt: 15.000 ms → 20.000 ms.

**What else changed:**

- Navigation failure and explicit interrupted Exit are preserved. Cancellation uses a socket readiness handshake and Fiber.interrupt, not a timed sleep.
- E-error total operation: 299.000 → 237.000 ms; release milestone → leader exit: 13.376 → 9.734 ms.
- E-interrupt total operation: 299.000 → 272.000 ms; release milestone → leader exit: 10.724 → 11.88 ms.
- No additional product behavior change observed in this fixture.

### N — close received, nonzero exit (harness)

[Complete paired transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/ca055e2ca2a57d56/row-N.txt)

| Before | After | Difference |
| --- | --- | --- |
| [dd14848153](https://github.com/pingdotgg/t3code/commit/dd148481531a0a090780a2ade20588566db099a2)<br>T3 TERM exits fake 0; preview succeeds | [c17d32c8ea](https://github.com/pingdotgg/t3code/commit/c17d32c8eabe2b9a8148ba1df30a53e7968a640c)<br>CDP close exits fake 23; preview still succeeds | 17.000 ms → 18.000 ms; zero After live survivors |

**Before → After difference:** T3 TERM exits fake 0; preview succeeds → CDP close exits fake 23; preview still succeeds; live survivors 0 → 0; milestone-to-return 17.000 ms → 18.000 ms.

**What else changed:**

- Nonzero child exit is ignored by graceful finalization. Existing spawner emits two ESRCH group TERM calls After; no live survivor.
- N total operation: 266.000 → 254.000 ms; release milestone → leader exit: 10.629 → 8.238 ms.
- No additional product behavior change observed in this fixture.

### A — actual AppArmor/LXC

**Not verified: AppArmor in LXC.** This VM reports AppArmor `N`; active LSMs are `capability,selinux`. No real policy-denial capture was possible. U does not establish policy behavior or sandboxed renderer teardown.

### X — refuses close and signals denied (optional)

Not run; unverified. No synthetic signal-denial injection was attempted. Universal leak prevention for an unresponsive and unsignallable process is not claimed.

### B — no acquired browser (optional control)

Not run; unverified. Missing-browser publish and startup/proxy failure controls were outside the required ten-row capture set.

## Regression and reproducibility

- F’s focused test fails Before (exit 1) and passes After (exit 0); both complete test logs are in [F](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d04ad44e69d25ce1/row-F.txt). Before had only the [test-file patch](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7350aee5eff36250/test-only.patch) applied, with production code unchanged.
- [Measured structured summaries](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/cc2e3dbcc082ddad/summary.json); [environment](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/65131dc0873f2fd5/environment.txt); [validation](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/53b1a8e3e2803176/validation.txt).
- [All raw captures, selected manifest, metadata, synthetic state and excluded pilots](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/6a7c7653afb58ebb/raw-captures.tar.gz). Browser download is excluded; its official pinned version/hash is above.
- [All scripts and SHA-256 manifest](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/b4b4a26d378a516b/scripts.tar.gz); [implementation-only diff](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7eb041954c9cdd54/implementation.patch); [friction log](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e3e895bca2508eb3/FRICTION.md).

- [capture.py](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/1590130c24ec0810/capture.py)
- [harness.mjs](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/4820d075dc75f005/harness.mjs)
- [fake.cjs](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7a8ea8b020d369ed/fake.cjs)
- [detached.cjs](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/02632cea3b1cee0e/detached.cjs)
- [real.py](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2862edfa28a95fc4/real.py)
- [real.cjs](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/92e52dca51673cab/real.cjs) — excluded Node-exec pilot launcher.
- [report.py](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/b92e2625fd09f2ee/report.py)
- [assemble.py](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dc4a1ece3e5fba30/assemble.py)

No commits or pushes. All captured processes were stopped by exact tracked PID, and the Before worktree is removed after upload. The After worktree remains untouched.
