---
name: checkpoint-wake-broken-postclear-forcenative
description: "ROOT CAUSE + SETTLED FIX (doyle revised C-1 @0c37e68, 2026-07-04) — checkpoint post-clear WAKE never fired because spt-core watchdog TERMINATED the healthy translate binary at the /clear boundary (eaten {commit} misread as fault). Fix = core miss!=fault (continue at broker.rs:1402, N=3 strike budget, no terminate); zero adapter change. Mine: loud-fail FIRE surface + eaten-{commit} WHY."
metadata: 
  node_type: memory
  type: project
  originSessionId: 6381498d-5efd-4af4-835f-b4ef61197264
---

**Operator hit it live (v0.13.0, hall-bf): the checkpoint post-clear wake command does not fire.** I root-caused with process-level proof and filed to doyle (HIGH). Diagnosis via /diagnose.

**ROOT CAUSE (spt-core, NOT adapter):** after a `/clear` boundary rebind, spt-core LOSES the endpoint's translation-binary force-native delivery registration. `spt send --force-native` returns `FORCE_NATIVE_UNDELIVERED: no live translation binary` even though the translate PROCESS is provably alive. The v0.12.0 checkpoint fires the wake via a POST-CLEAR force-native (`{checkpoint_fire}` from the SessionStart hook) — exactly the dropped window → wake silently never delivers.

**Precision repro (fresh single-translate probe wakep2, translate pid=402120):**
```
PRE-CLEAR  force-native → SENT           (402120 alive=True)   [works before the clear]
ARM SENT → CLEAR fired
POST-CLEAR ×5 over 40s → FORCE_NATIVE_UNDELIVERED every time, 402120 alive=True every time
```
Persistent ≥40s, does NOT recover, process never dies. Not a race, not process death, not an adapter bug.

**H1 (two-translate double-spawn) was a RED HERRING** — operator corrected: 2 live endpoints (hall-bf + flynn) = 1 translate each, correct. My repro confirmed: 2 translates before bringup, 3 after adding a probe = one per endpoint. Anchored on it initially; user's domain knowledge killed it (why Phase-3-show-user matters).

**This is the B6 GHOST, now deterministic.** The whole B6 live-repro hunt (2026-07-03) saw intermittent "force-native undelivered with translate alive" and couldn't pin it across a 5-cell matrix. The trigger is a /clear boundary — B6 probes had crossed clears. B6's force-native decay = THIS. [[b6-liverepro-hunt]]

**Why uncaught:** the v0.12.0 checkpoint WAKE-half was NEVER live-verified — deferred every session, then tangled in B6. The race-fix half (clear-only ARM) gate-passed; the wake-half shipped broken through v0.12.0/0.12.1/0.13.0. RC int #4 tested the is_spt_hosted_no_relay GATE health, NOT actual wake delivery.

**FIX = spt-core** (filed): restore/re-establish the translate binary's force-native delivery registration across a /clear|compact boundary rebind (process survives by design — "reaped on shutdown NOT on /clear"; only the delivery channel/liveness marker is dropped). Adapter CANNOT work around: fire-and-forget retry won't deliver while force-native stays UNDELIVERED the whole window; design deliberately avoids active-poll (ENLYZEAM plain-text misdelivery). **Adapter follow-up (mine, once core delivers): make the FIRE (hook.rs:512) CHECK its result + surface a loud failure instead of silent fire-and-forget** — so a future regression is visible, not silent.

**REFINEMENT (captured-stderr iso trace, 2026-07-04):** on a HEALTHY iso daemon (single endpoint, fresh, healthy pump), post-clear force-native RECOVERS at ~t+20s (one reconcile/pump cycle) — CONTRADICTS the box (persistent ≥40s, never recovered). session_id ROTATED across the clear (confirmed). Reading: registration re-establishes LAZILY at the next pump/reconcile cycle; healthy pump → ~20s recovery; stalled/slow pump (busy multi-subnet box — B6 pump-stall) → never reconciles → persistent loss. **This EXACTLY explains B6's intermittency** (self-heal at the pump cycle masked a persistent between-pump loss; stalling the pump killed the self-heal). Fix points at the REBIND path (eager/synchronous re-registration at the boundary, not lazy-at-pump). Ask-#2 keying: not stderr-loggable; session_id rotation is CONSISTENT with session-keyed registration (doyle's code-RCA: try_spt_hosted_inject registration keyed by session_id vs endpoint_id).

**MECHANISM CONFIRMED (grep #1 + doyle broker-map, 2026-07-04):** the ARM's clear-only inject (ctrl+s·/clear·enter·commit) drives /clear; the TUI churn/teardown EATS the {commit} detection; the core watchdog faults+TERMINATES the translate binary that was driving it (`TRANSLATION_FAULT: no {commit} within INJECT_COMMIT_DEADLINE`); pending_wake dies in-memory; ~20s "heal" = lazy respawn (pump-dependent); stalled pump = persistent. Faulted latch → delivered=false → CLI prints generic "no live translation binary". B6 fully explained. Deterministic at the boundary.

**FIX — CORRECTED (operator overrode doyle's b3 ruling, perri agrees, 2026-07-04):** doyle first ruled b3+retry, but the OPERATOR caught that **b3 does NOT avoid a holdover file**: the wake text originates at ARM time (PostToolUse hook); the FIRE is a SEPARATE process (SessionStart-clear hook) that knows only "clear happened" and has NO access to the wake text — carrying a CUSTOM wake in the FIRE envelope requires the ARM to persist it = a file (the b1 hazard; env-file carriers also die per F-024). b3 is only file-free for the compile-time DEFAULT wake. AND eager-respawn alone loses pending_wake (empty respawn). **THE CLEAN FIX: spt-core must NOT TERMINATE the translate binary at a /clear boundary.** The binary is healthy — the watchdog faults it only because the {commit} is eaten by the /clear teardown the inject drove (self-inflicted, expected, not a real fault). Don't-terminate → in-memory pending_wake SURVIVES → original v0.12.0 design works → ZERO adapter change, ZERO file. spt-core options: don't arm INJECT_COMMIT_DEADLINE for a clear-driving inject; OR exempt missing-commit-during-known-clear from terminate; OR treat clear-disrupted-commit as expected. b3 RETRACTED.

**SETTLED (doyle re-ruled 2026-07-04, pushback ACCEPTED — revised C-1 committed @0c37e68, supersedes @5dba61c):** miss != fault. Doyle verified structurally: b3's custom-wake path necessarily reintroduces the holdover file (ARM originates wake text; FIRE is a separate process), and terminate-then-respawn guarantees pending_wake loss. Code-verified feasible: today's failure is literally `return` at broker.rs:1402 (worker exits on FIRST miss); fix = `continue` (flush+release floor / anti-stall / respool unchanged), no terminate, no faulted latch → binary + in-memory pending_wake survive the boundary → v0.12.0 wake design works as intended. Wedge protection = strike budget (N=3 consecutive misses → real fault); real faults get bounded respawn + perch-visible fault stamp. Loop-top stray-drop (broker.rs:1337) already neutralizes a late {commit} from a missed sequence. Core lands via todlando (W2 GO includes wake-survival e2e; W1 gated PASS 2026-07-04 night).

**(B) ZERO-RECORD FIRE PINNED (2026-07-04, doyle 3-discriminant collab; operator caught two-commit premise wrong first):** installed v0.13.0 binary verified emitting `{"commit":true}\n` (clean bytes) after BOTH enters — adapter choreography was never the gap. Doyle offered 3 receive-side mechanisms; discriminants: (A) whole-sequence-deadline EXCLUDED (budgets ≤~150ms vs 5s); (C) parse-drop EXCLUDED (0 BAD_LINE hits, clean commit bytes); **(B) PINNED: FIRE-with-nothing-armed returns Vec::new() (translate.rs:280) = zero records, no bare {commit} → broker waits full 5s → TRANSLATION_FAULT + terminate.** Iso wtrace stderr holds it: 4 injects ([pre], ARM w/ accepted commit, fire#1 drains wake fine, fire#2 unarmed) → fault is final line ~5s after the DUPLICATE fire. Since SessionStart self-sends checkpoint_fire on EVERY clear, every non-checkpoint /clear + every duplicate fire kills the binary. "Eaten {commit}" attribution DEAD. **CAVEAT: box wakep2 (immediate post-clear UNDELIVERED, never recovers) NOT explained by (B) — boundary registration-loss on stalled pump = second, distinct core seam.** Core contract gaps doyle amends: (1) published docs say missed-commit ⇒ raw-inject fallback — STALE, raw-inject removed v0.14.3, real penalty = permanent terminate; (2) "empty response still requires a bare {commit}" documented nowhere. **Adapter v0.13.1 SHIPPED 2026-07-04 (@52dc092, doyle technical GO + operator standing order): nothing-armed fire + event-without-envelope answer bare {"commit":true}; init/input stay silent (commit belongs to a sequence — doyle scoping). REQ-HAZARD-EMPTY-RESPONSE-COMMIT minted (doc KH §2.7 + impl + 3 units green); boundary-survival int reserved for the post-C-1 wake-survival e2e. Node applied; running translates hold the 0.13.0 image until endpoint respawn. Doyle folded into core C-1 off my findings: respool-once/dead-letter (kills the infinite redeliver-miss cycle under no-terminate) + FORCE_NATIVE_UNDELIVERED leg-differentiated stderr (CLI-gate vs no-session vs spooled — unlocks pinning SEAM-2, the box's immediate-UNDELIVERED registration loss, from field output).**

**MY REMAINING SIDE (both cheap, my pace):** (1) loud-fail surface on undelivered FIRE — keep, regression visibility; (2) find WHY the {commit} is eaten during the clear the binary drove — core fix independent of the answer, but it decides whether the commit-less-inject protocol seed (b2) stays parked. `scratchpad/rc-ints/wake-repro.sh` graduates to the wake-survival e2e once core C-1 lands.

Superseded first ruling below kept for history:

**(superseded) doyle-RULED (F-029, red-first):**
- (A) CORE (todlando, F-029 W2, doyle sequences): EAGER FAULT-RESPAWN — when the watchdog faults+terminates the translate binary, respawn it IMMEDIATELY (bounded backoff, C3(b) crashloop-backstop pattern), NOT at the lazy reconcile. Bounds EVERY dropped window (not just checkpoint), kills pump-health dependence. Likely + a PERCH-VISIBLE fault stamp (TRANSLATION_FAULT is daemon-stderr-only today = the F-019 channel trap). Shrinks respawn window to ~1-2s.
- (B) ADAPTER (MINE, build NEXT session): **b3 + retry** (RULED). b3 = the checkpoint_fire envelope CARRIES the wake payload → the fresh post-clear binary emits straight from the FIRE envelope, STATELESS across the boundary (no in-memory pending_wake to die). KEEP retry-until-delivered wrapped around it (deadline >= respawn bound + margin, >=30s; loud fail after) — the FIRE still has to straddle the respawn window; with (A) it lands on the 1st-2nd attempt. **b1 REJECTED** (persistent pending_wake file = stale-state/drift, F-015B hazard class). **b2 PARKED** as a choreography-contract-hardening seed (commit-less inject variant for self-disrupting sequences; manifest-contract change, not interim). Change = translate.rs (dispatch_event: checkpoint_fire reads wake from its own envelope, drop the pending_wake cross-boundary reliance) + hook.rs (FIRE payload carries the wake; retry loop + deadline + loud fail) + wake-repro.sh regression. Grep #2 NO LONGER NEEDED (fault line answered it).

**POST-0.13.1 VERIFY (2026-07-04, operator full-restart of hall-bf, wake STILL dead):** precision probe wakep3 on the box daemon (adapter 0.13.1 confirmed): **kill-fix HOLDS** (translate pid alive end-to-end through boundary + 6 unarmed fires — previously fatal) but **SEAM-2 is THE live blocker**: ARM SENT → clear t+10s → FORCE_NATIVE_UNDELIVERED "no live translation binary" ×6/42s with process alive each measurement, NO recovery. Pure form: no fault, no terminate, binary healthy — spt-core loses the translate registration across the /clear rebind and the box never lazily re-establishes (stalled-pump profile; iso healed ~20s). Hook's boundary fire hits the same wall → pending_wake never drains → wake dead until core fixes the rebind registration (eager re-registration ask sent to doyle for C-1 scope). NOTHING further adapter-side. NB probe's transcript-marker assert self-contaminated (grep matched MY session transcript carrying the marker string from the script source — probe cwd transcript had NO hit; wake did not land). Harness: session scratchpad wake-precision-0131.sh.

**SEAM-2 PINNED (2026-07-04, wakep9 + wakep4 control, doyle ask-#1 gate-state dumps): READY-FILE MISSING — CLI leg (1).** At every post-boundary UNDELIVERED instant exactly ONE gate field differs from the healthy no-boundary control: `owlery/<id>/ready` ABSENT (info.json stays online+controllable, sid rotation itself WORKS, translate alive). `try_spt_hosted_inject` Nones out on the missing ready file BEFORE any broker RPC. The /clear boundary rotation kills the ready stamp; healthy-daemon pump re-stamps ~20s (the iso heal), busy box never does. Fix fork sent to doyle: (a) rotation re-stamps ready atomically with sid, or (b) gate stops keying on ready when info.json healthy. Wave context: F-029 fully gated incl. C-1 @15d48bf (miss!=fault + UNDELIVERED-by-leg + empty-response docs rule); v0.24.0 RC cut waits on this verdict fork (small pre-RC chunk vs post-wave REQ). Evidence: wakep9 (b6n8a851g) vs wakep4 control (bgzct9ldr); never-came-up junk runs wakep6/7/8 were the ENOSPC trust-wipe collateral ([[claudejson-write-hazard]]), abort-guard hardened.

**v0.24.0 RC WINDOW (2026-07-04, sha d89307a6…, BOTH ITEMS RC-GREEN, flip unblocked):** C-1 CONFIRMED (translate survives every boundary, zero faults, no permanent force-native death), C-2 CONFIRMED-STRONG (no ready-absent window at 150ms resolution; 157/157 boundary-spanning fires SENT, 0 UNDELIVERED — SEAM-2 eliminated, leg labels unexercised). **BUT WAKE STILL DOESN'T EXECUTE — LAYER 4 PINNED, PRE-EXISTING (identical on 0.23 control, PTY --view screen evidence):** ARM inject lands "native mid-active" → CC QUEUES the injected /clear (prompt-queue) → clear runs from queue (sid rotates) → fresh session restores a STALE "/clear" draft in the input box + the WAKE sits in the prompt queue UNSUBMITTED, frozen 60s+. Pre-clear inject+submit works (standby turn executed). **MECHANISM RETRACTED (operator correction, 2026-07-04): the "CC queues the mid-active /clear → restores it as a draft" chain is WRONG — a submitted /clear is consumed deterministically.** Evidence-only F-031 shape: ONE ARM inject logged, ONE clear executed (single sid rotation), then the screen shows "/clear" typed a SECOND time unsubmitted in the input box + wake unsubmitted in the prompt queue, frozen. OPEN: (1) what typed the second /clear (only actor = ARM clear-half, logged once — keystroke replay across the boundary without a second logged delivery?); (2) why nothing submits post-boundary while pre-boundary inject+submit provably works. "native mid-active" tell = real observable, causal role UNPROVEN. Blind extra enter still unsafe (would submit the sitting /clear = double clear). Correction sent to doyle for the seed. Layer stack now: (1) zero-record kill [v0.13.1 ✓], (2) miss=fault terminate [C-1 ✓], (3) ready-strand [C-2 ✓], (4) CC queue capture [OPEN]. Side finds: bringup dead-harness flake not version-pinned (2/8 vs 0/9, controlled batch 6/6 green); broker-PTY sessions leave NO transcript jsonl (assert channel — use side-effect proof files); LIVEHOST_PSYCHE_NONRESIDENT in iso runs (F-030 floor evidence). Rigs: session scratchpad rc44/.

**ARC CLOSED 2026-07-04 (wakefin3 GREEN): full five-layer stack proven end-to-end on box 0.24.0 + adapter v0.13.3 — ready persisted, ARM SENT, boundary t+8s, WAKE TURN EXECUTED (+12s side-effect proof file), translate alive, post delivery SENT. F-031 opens collapsed (frozen state = the enter-coalescing, fixed + disproven-by-execution).** **BAD-SHIP INTERLUDE: v0.13.2 accidentally carried the PARKED ephemeral-psyche build (cut from HEAD — publish was F-030-gated); on no-F-030 cores the shim's by-design exit tripped daemon residency → fresh live endpoints lost their ready stamp seconds after bringup, force-native gated forever (leg=cli-gate-not-hosted — C-1 leg labels' first field sighting); hall-bf itself hit it (psyche_host_error attempts=54, doyle field case #2). CONTAINED: revert on main, work parked on branch `psyche-ephemeral-parked` for F-030, v0.13.3 = settle fix on resident psyche, Latest flipped, 0.13.2 marked superseded/do-not-install. POST-MORTEM RULE: never tag from HEAD carrying parked gated-publish work — runbook needs a parked-work exclusion check pre-tag.** wake-confirm-0133.sh = the graduated wake-survival e2e (promote to repo int, REQ-HAZARD-CHECKPOINT-CLEAR-RACE int successor).

**LAYER 5 FOUND + FIXED — v0.13.2 SHIPPED 2026-07-04 (@363ecab, node applied → superseded by v0.13.3 @7f70114):** operator bounced the box to 0.24.0 and the wake ARRIVED for the first time (C-1/C-2 field-working) but sat UNSUBMITTED with a trailing newline. Cause: clear/wake halves lacked the 50ms text→enter settle that commands_for_event always had — the discrete enter's \r coalesced into the still-landing text = SOFT NEWLINE not submit (short "/clear" usually wins the race; paragraph wakes reliably lose). Fix: settle added to both halves (ctrl+s·50·text·50·enter·commit), unit + packed-artifact verified, floor stays 0.23.0. **Likely REFRAMES F-031: the frozen queue/second-/clear observable was probably this coalescing all along (both enters eaten), not a CC queue anomaly — confirm via next live checkpoint on a bounced endpoint, then the F-031 opens may collapse.** Layer stack: (1) zero-record kill [0.13.1 ✓], (2) miss=fault terminate [C-1 ✓], (3) ready-strand [C-2 ✓], (4/5) enter-coalescing soft-newline [0.13.2 ✓ pending live proof]. Resident translates hold 0.13.1 until endpoint respawn.

**PREVENTION (post-mortem):** a live WAKE-DELIVERY E2E that asserts the wake TEXT (a marker) lands in the fresh post-clear session — not just gate health. Reusable regression harness built: `scratchpad/wake-repro.sh` (drive ARM → clear → assert marker in fresh transcript) + `wake-precision.sh` (translate-alive-vs-force-native at the post-clear instant). Promote to a real int when core fixes it.

Related: [[b6-liverepro-hunt]], [[v0120-checkpoint-race-project-names]], [[counter43-rc-window]].
