---
name: v0121-l1-viewer-close-detach-findings
description: v0.12.1 L1 VIEWER-CLOSE-DETACH — breakaway impl+units LANDED @5ae68f8 + load-bearing empirical finding (a job CAN deny breakaway); int blocked on doyle ruling + operator real-env data
metadata: 
  node_type: memory
  type: project
  originSessionId: 38cea990-23ee-4d46-a1f1-5e765e00b7a0
---

v0.12.1 Wave 2 **L1** (REQ-HAZARD-VIEWER-CLOSE-DETACH). Partial LANDED on branch v0.12.1-lifecycle **@5ae68f8** (doc+impl+unit; traceable EXIT=0; clippy --workspace clean). Follows L0 @cf5eab4. See [[v0121-l0-attach-deadlock-root]].

**Bug:** closing the terminal tab where `endpoint run` ran kills the daemon-hosted harness — the daemon (freshly autostarted by THIS endpoint run via `ensure_running`→`detached_no_inherit`) inherits the terminal's Windows Job Object (KILL_ON_JOB_CLOSE); tab-close drops the job's last handle → OS reaps the daemon + its broker-spawned ConPTY harness subtree.

**IMPL LANDED (both daemon spawn paths, best-effort + fallback):**
- `daemon.rs` `detached_no_inherit`: try `BASE_FLAGS | CREATE_BREAKAWAY_FROM_JOB(0x0100_0000)`; on `ERROR_ACCESS_DENIED(5)` retry in-job (logs `DETACH_BREAKAWAY_DENIED`). NO spawn regression. `detached_no_inherit` is SHARED with `shellhost::launch_shell` (both want job-independence — fine).
- `deelevate.rs` `create_with_token` (elevated `CreateProcessWithTokenW`): same flag, fall back on `INVALID_PARAMETER(87)`/`ACCESS_DENIED(5)` (restricted-flag-set API may reject it).
- units: `detached_no_inherit_falls_back_under_a_breakaway_denying_job` (no-regression, CI-green — the runner job denies breakaway so this hits the fallback directly) + `breakaway_spawn_escapes_a_kill_on_close_job` (OS escape mechanism via self-reinvoke-into-job; self-SKIPs where the ancestor job forbids breakaway).

**LOAD-BEARING EMPIRICAL FINDING:** the cargo/CI **test-runner's OWN ancestor job FORBIDS breakaway** — `creation_flags(0x0100_0000)` on a child → `ERROR_ACCESS_DENIED(5)`. Proven directly + via `IsProcessInJob` (a breakaway grandchild stayed `gc_in_any_job=1`). Consequences:
1. The "harness survives tab-close" **int gate cannot run faithfully in CI**: every spawned proc nests under the runner job → breakaway can't isolate a single job → false red. A green there would be FALSE-GREEN (the v0.12.0 trap).
2. breakaway only escapes a job that sets `BREAKAWAY_OK`/`SILENT_BREAKAWAY_OK`. cargo's runner proves a real job CAN deny it. **IF the operator's WT/VSCode job likewise denies breakaway, breakaway ALONE does NOT fix the bug.** The daemon-owned harness Job backstop does NOT save it either (a nested job can't protect members when the ancestor job is killed).

**BLOCKED → doyle ruling (sent @5ae68f8) + OPERATOR real-env data:** (a) accept breakaway as primary fix + int = operator real-terminal acceptance step? (b) design a fallback escape now (Task-Scheduler relaunch / detached re-exec outside any job) in case WT denies breakaway, or wait on operator data? (c) harness-Job backstop folded into **L4** daemon-stop-reap (its real value), NOT L1 — ok?

**OPERATOR ACTION (the real acceptance test):** on the real Windows Terminal / VSCode, `endpoint run` a harness in a fresh session (daemon autostarted by it), close that tab, then `spt rc <id>` — harness must STILL be alive + re-attachable. If it dies, WT denies breakaway → escalate to the (b) fallback design.

**Carried:** Unix guard test (setsid already detaches the daemon session from terminal SIGHUP — "no code expected") deferred to the int wave. required_stages currently `[doc,impl,unit]`. NEXT independent work = **L2 ATTACH-WEDGE** (nethost.rs loopback fail-fast, doesn't depend on this ruling).
