---
name: v0121-l0-attach-deadlock-root
description: v0.12.1 L0 keystone REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT root cause — serve_attach replay-vs-forward IPC deadlock
metadata: 
  node_type: memory
  type: project
  originSessionId: 3acfe970-b087-4764-8a3e-c2253b8bd0ae
---

v0.12.1 Wave 2 **L0 KEYSTONE root-caused** (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT, the operator's "attach shows no output"). Branch v0.12.1-lifecycle @0d8ed2e (doyle's minted REQ). Found via instrumenting the real dummy fixture (`dummy_harness_e2e.rs`), NOT static reading.

**ROOT (definitive):** a broker REPLAY-vs-FORWARD IPC deadlock in `serve_attach`, triggered ONLY by a non-empty `OutputLog` ring at attach = an already-producing harness = exactly endpoint-run. `serve_attach` receives session Output (broker→serve) AND sends its forwards (serve→broker) on the **same** broker IPC connection. `become_controller` (broker.rs ~249) does a synchronous **multi-frame** ring replay inline inside `dispatch_subscribe`, holding the broker's single-threaded per-conn handler in the broker→serve write loop → it can't read serve's serve→broker `net_stream_send` forwards → both directions back up → mutual backpressure deadlock → operator (rc) gets 0 bytes.

**Ruled OUT:** drain works (ring fills to 9), not the dummy program, not worker starvation (tried net runtime 2→8 workers, nethost.rs:653, no change). Smoking gun: serve prints SERVE_FWD seq0/seq1 but broker-side `send_stream(stream=1)` NEVER runs → loopback op_r read pump never reads.

**Why green-but-broken:** known-good `local_attach_via_loopback_conn_rides_the_same_pump` attaches to an IDLE echo child (empty ring → 0-frame replay) and forwards ONE chunk; cwd E2E reads on the spawning conn (no serve-over-IPC). Neither exercises full-ring replay + sustained forward on one conn. **Likely cross-node too** (same serve_attach shape over QUIC).

**SHIPPED (FIXED + committed @cf5eab4, branch v0.12.1-lifecycle):** doyle ruled Option (a). `serve_attach` now forwards all 7 wire sends on a SEPARATE `Brain::cold_start` conn from the one it subscribes/receives on (`broker_name` threaded to serve_attach + dispatch + 4 test sites). Order preserved (one serve thread), `delivered_through` cursor untouched. Result: rc_saw_tick=true, 230 bytes delivered, run 13s→2.8s. REQ activated [impl,unit,int]; unit = `loopback_attach_to_a_prepopulated_ring_delivers_without_deadlock`; int = dummy fixture `rc_saw_tick` hard assert (old `rc_connected` proxy now informational — raced the fast teardown). Gate-green local: attach 13/13, dummy green, clippy --workspace clean, traceable EXIT=0. SEAM-GREEN pinged to doyle for independent re-run. Option (b) (unify controller replay onto viewer async writer-thread pattern) flagged POST-v0.12.1 in commit+REQ. NEXT: L1 VIEWER-CLOSE-DETACH + L2 ATTACH-WEDGE.

**Original fix options (doyle picked a):** (a) serve_attach forwards on a SEPARATE broker conn from the one it subscribes on — surgical, `send_stream` is stream_id-keyed/conn-agnostic; my lean for v0.12.1. (b) make `become_controller` replay non-blocking via the dedicated-writer-thread+bounded-channel pattern `add_viewer`/`viewer_writer` already use — more complete (fixes cross-node) but touches the authoritative cursor-advancing controller path. Then activate REQ stages [impl,unit,int] + flip fixture `rc_saw_tick` diagnostic → hard assert. See [[v0121-lifecycle-realharness]] [[v0121-realharness-reopen]].
