---
name: v0394-field-bugs-hertz-rca
description: "4-bug v0.39.4 field series operator-handed to hertz for RCA 2026-07-21; doyle waits for hertz root-cause reports, NOTHING to act on before they land"
metadata: 
  node_type: memory
  type: project
  originSessionId: c10d88a3-e033-4855-b9d8-6c256f3e18d8
  modified: 2026-07-22T09:31:45.025Z
---

**Operator 2026-07-21 (screenshot `C:\Users\decid\Documents\ShareX\Screenshots\2026-07\WindowsTerminal_0njiA5fZcU.png` = the brief hertz received): spt-core fully restarted on v0.39.4; hertz RCAs and reports root cause + recommended fix TO DOYLE. Doyle acts only when hertz reports.**

The four bugs, with the standing context each will land on (context, NOT pre-anchoring — [[ground-dont-assume-on-incidents]], hertz owns the RCA):

1. **todlando false-ONLINE after core restart** (marked online, session not running). Adjacent standing seed: DAEMON-LIFECYCLE C1 hybrid-ONLINE classification + skipped reconcile in [[spt-core-findings-backlog]].
2. **Windows `spt daemon stop --force` non-terminal**: stops once → 5-10 ephemeral windows spawn in sequence (appear/disappear) → daemon running again; takes 4+ force-stops to stay down. Lands on [[teardown-authority-progress]] — v0.39.0 shipped "honest tree teardown for stop/shutdown" + "guarded purge --force" and that milestone is STILL OPEN un-field-verified; this looks like a field RED against that claim class (respawn = supervisor/self-heal resurrecting the tree?).
3. **Garbage chars / noisy PTY STILL NOT FIXED after third iteration.** Operator emphatic: NOTHING to do with resize — triggers naturally scrolling claude code message history or navigating menus; absent in non-spt-hosted sessions; had an Image #1 (634x746) attached to hertz's copy. Lands on [[rc-render-truth-progress]] (v0.39.3 geometry-epoch + v0.39.4 presentation barrier were the resize-scoped iterations). Candidate standing frame if hertz's RCA points there: the build-ready ScreenGrid Unicode width-defect seed (REQ-SCREENGRID-WIDTH in [[spt-core-findings-backlog]]) — its signature is exactly non-resize, menu/dense-Unicode, synth-repaint scraps. hertz already owns that seed's screenshots.
4. **Every `spt update` freezes all session PTYs**, recoverable via detach + `spt rc` — operator calls it a regression of a bug "fixed many versions ago" = the REDISPATCH-TRUTH freeze shape ([[redispatch-truth-progress]] closed; its fix suite = first regression suspect surface, or a NEW mechanism with the same symptom — do not re-anchor old theories per [[update-wedge-2-resume-steal]]).

RC-RENDER-TRUTH interaction: hertz's dispatched field-verify (seam 1 resize legs / seam 2 lifecycle) is presumably now folded into or sequenced with this RCA batch — bug 3 existing does NOT auto-fail the v0.39.4 milestone seams (those were resize-scoped); keep verdicts separate, ask for the discriminating observable if hertz conflates.

**DOYLE VERDICTS 2026-07-22 (hertz RCA received + seam-checked same day; reply sent):**
1. **ACCEPTED FULL** — bare-PID custody ABA seam-verified (livehost.rs:571 restart gate + :822 reconcile DEFER, both `read_resume_pid(...).is_some_and(is_process_alive)`, zero identity binding); field proof direct (resume.pid=29456 → unrelated cmd.exe). Fix: PID+creation-time custody, atomic clear on bind/reap, mismatch→delete+proceed. Triage kin: DAEMON-LIFECYCLE C1.
2. **ACCEPTED FULL** — api/mod.rs:349 unconditional ensure_daemon() on every api call = hook-driven respawn convoy after stop (rc.rs:1715/:3458 fixed the same bug rc-side only). Fix: durable operator-stop inhibit pre-teardown + machine-lock spawn serialization. ⚠ amends REQ-DAEMON-3 contract — ADR paragraph required.
3. **REJECTED AT SOURCE** — claimed root (status-row DECSTBM virtualization) is dead code: `STATUS_ROW_ENABLED=false` since a93ddba/v0.19.0; every margin/rows-1/reassert leg gated on it (rc.rs:208/215/1906/2203/2251/2327). Discriminator demanded: client-side byte capture showing a margin assert during repro. Counter-frame sent: hertz's OWN 2026-07-19 ScreenGrid width RCA (REQ-SCREENGRID-WIDTH, build-ready) matches the full symptom (dense-Unicode menu/scroll, constant geometry, spt-hosted only); asked for wide-glyph-correlation + pure-ASCII-TUI discriminator run.
4. **DIRECTION ACCEPTED, pin outstanding** — self-supersession via the ADR-0044 gen ladder (same identity + newer gen → loud revoke, by design per the 2026-07-18 T6 ruling); a duplicate establish w/ gen+1 for one viewport kills the client's own writer while its pump stays on the old stream. Pin needed from hertz: the two subscribe records' (by, attach_gen, conn id) for one frozen viewport → discriminates rc-reconnect-retry vs resume-fanout vs redispatch-replay origin. Fix direction: idempotent attach transaction keyed (viewport id, session generation); update completion gated on post-restart controller repaint.

**HERTZ CORRECTIONS 2026-07-22 (round 2, both doyle-accepted):**
- **Bug 3 root WITHDRAWN by hertz** post source-check ("overfit dormant code"). New evidence kills width-as-THIS-root too: corrupted screenshot is overwhelmingly ASCII + contains a leaked literal `[1m` tail (ESC eaten, remainder rendered as glyphs). Incident root OPEN. Discriminator protocol ratified: capture A (ConPTY child bytes pre-ScreenGrid) / B (broker output frames) / C (client terminal input) + attach-repaint dump; pure-ASCII menu/history nav at fixed geometry; zero-resize + zero-DSR/CPR assertions; `[1m` only-in-synth-repaint ⇒ ScreenGrid parser-fidelity (cells storing sequence bytes, render_repaint re-emitting) vs in-raw-A ⇒ ConPTY/child. ⭐ WIDTH seed (REQ-SCREENGRID-WIDTH) grounding UNTOUCHED — stays build-ready as an INDEPENDENT defect on its own 2026-07-19 wide-glyph evidence; just not claimable as this screenshot's root. Early hertz-side kill test: corruption repro under pure ASCII (no core taps needed).
- **Bug 4 pin blocked on instrumentation**: production breadcrumbs (conn19/stream3 etc.: stream-sub-attach → controller-attach → controller-replaced new_conn=SAME → second controller-attach → writer-exit channel-closed) lack attach_gen/lease id — retroactive attribution among rc-retry/update-fanout/redispatch-replay impossible. Breadcrumb shape ratified: resolve_subscribe decision record (endpoint/session, by, conn, stream, viewport/lease id, old_gen, requested_gen, decision) + rc establish-attempt record (attempt #, reconnect epoch, op id). One update repro after = the pin.

**BUG-3 ISOLATED RIG RESULT 2026-07-22 (hertz, same day): NOT REPRODUCED — isolation sensitivity is now the discriminator.** Rig was honest: exact f31849c debug build, SPT_OBS_BYTE_TAP=1, separate home/broker, fixed 120x40 RC, 0 DSR + 0 resize asserted, trust-screen→main transition + /help menu nav exercised. A (raw PTY) and B (broker frames) BYTE-IDENTICAL; independent pyte 0.8.2 replay of raw-only vs synth-repaint+raw = 0 differing rows. One candidate FALSIFIED en route: OMP-hub-log run-together text = log rendering, not PTY corruption. So the field root needs something the clean rig lacks (production history depth/content, load, viewer topology, resize history — enumerate at triage, don't assert). ✅ ESCALATION RESOLVED BY OPERATOR 2026-07-22: no HFENDULEAM channel flip — hertz runs the bug-3 RCA on **enlyzeam** (operator-directed). Bug-3 FIX ROLLS INTO DAEMON-LIFECYCLE when hertz's RCA lands (operator ruling). Until then open-with-protocol, symptom un-attributed.

**BUG-3 CAPTURE PATH RULING 2026-07-22 (doyle):** hertz can't run the A/B/C capture on stock 0.40.0 (byte taps debug-gated, compiled no-ops) and debug-pinning HFENDULEAM was REFUSED as a doyle-solo grant (debug-pin flips the node's release-keys.json to channel=debug = stops accepting stable offers; broker-touching debug apply needs a fleet-quiesce window — both operator-consequence). AUTHORIZED INSTEAD: isolated-SPT_HOME debug rig — build f31849c (the v0.40.0 tag commit) debug-profile into a throwaway target, separate home+socket, host a claude session in-rig, drive the natural trigger (scroll history/menus), capture per ratified protocol. DEBUG-ROLLOUT only forbids replacing the PRODUCTION binary; isolated daemon = the established field-repro pattern. Non-repro in isolation = a FINDING (isolation-sensitivity discriminator) → then doyle escalates the debug-channel request to operator. Same rig doubles for NOTIF observable 3 (debug-rollout lab flow inside the isolated home mints a real signed offer).

**BUG-4 TRIGGER RULING 2026-07-22 (doyle, code-grounded):** hertz's node is at latest (0.40.0, version gate PASS) so no natural update trigger exists. Debug/monotonic staged set on a production node REFUSED (signed verify/classify/quarantine machinery; cache pollution). `spt daemon refresh` AUTHORIZED as the exact-seam trigger: refresh_brain (applyhost.rs:364) raises the SAME no-arg `request_brain_restart` (brain.rs:1743) the post-swap apply rides (applyhost.rs:339) — broker cannot distinguish triggers; session preservation is a property of BOTH paths (apply too, :320-322), not a refresh-only limiter — the freeze is client-side re-establish racing the fresh brain's dispatcher, on top of preservation. Refresh skips only swap/verify/trial bookkeeping (none on the reconnect path). Pin capture: refresh with breadcrumbs armed against the live `spt rc lia` viewer (pid 55192); label trigger=refresh in the verdict. Contrapositive is signal: refresh-never-reproduces + update-reliably-freezes discriminates toward the skipped deltas — report, don't force.

**BUG-4 PINNED + ACCEPTED FULL 2026-07-22 (hertz capture on authorized refresh, doyle source-verified same hour):** the gen+1 premise FALSIFIED — root is SAME-generation SAME-conn self-supersession during dispatcher replay. Evidence: no RC_ESTABLISH/RC_RECONNECT during refresh (rc retry excluded); replay opened conn1011; SUBSCRIBE_DECISION #1 old_gen=0 → controller; 15ms later #2 SAME conn/by/req_gen with old_gen==req_gen → controller + controller-replaced new_conn=1011 → writer-exit channel-closed. Source: broker.rs:1807-1810 equal-gen branch = "silent re-take" (no revoke — designed for the DEAD-seat dispatcher-restart case) but calls become_controller, and broker.rs:1385-1390 unconditionally takes+drops the prior seat with NO same-conn check → same live conn kills itself. FIX CONTRACT (banked): idempotent reuse in resolve_subscribe's equal-gen branch keyed (same by, same conn, same gen, same session) — seat+writer preserved, no controller-replaced, no second initial batch; equal-gen DIFFERENT-conn keeps today's swap (fix-6 successor, must not regress); decision=idempotent breadcrumb label (additive); OPEN sub-question banked: why the dispatcher double-served one held attach 15ms apart. Regression matrix: replay-after-refresh one-writer leg / different-conn swap preserved / ladder above+below unchanged / breadcrumb assert. Bugs 1+2+4 now ALL build-ready → DAEMON-LIFECYCLE fold-in, operator ranks queue. Field workaround stands: detach + fresh rc.

**Queue proposal (needs operator):** (i) small debug-gated **OBS rider wave** = bug-4 breadcrumbs + bug-3 A/B capture taps — files (broker.rs, rc.rs, term taps) ZERO overlap with NOTIF-TRUTH W2 producer/delivery files, can run parallel per [[dispatch-on-overlap-not-on-merge]]; (ii) bugs 1+2 build-ready NOW, fold into queued DAEMON-LIFECYCLE; (iii) bug 4 joins once pinned; (iv) bug 3 open-with-protocol. NOTIF-TRUTH W2 still holds the lane (todlando holding on operator).
