# RCA — `gears` / `lia` unreachable by `spt rc` and the picker

**Status:** mechanism LOCALIZED, not yet closed. Author: hertz, 2026-09-07.
**Specimen:** `.spt/preserved/gears-specimen/` (19 files, captured 2026-09-06T23:14:22Z, BEFORE
the stop/start that changed gears' state — unreproducible). Preserved out of a dead session's
`%TEMP%` on 2026-09-07; see `scratchpad-is-not-preservation`.

## Symptom

Operator-facing, live. Endpoints started in certain directories cannot be reached by `spt rc` or
the picker. `gears` (new) in `travelear` and `lia` (months old, previously healthy) in both
`travelear` and `liaison` both fail; `lia` works in `Documents/projects`.

## ⚠ The directory correlation is NOT yet a mechanism

`projects` is pinned as a startup endpoint for `lia` in `daemon.json`
(`startup_endpoints[].cwd = C:\Users\decid\Documents\projects`), confirmed firing in the log as
`ENDPOINT_AUTOSTART:lia adapter=claude-spt session=5203aed4eedda180 pid=27368`. That explains why
`projects` is her *habitual* cwd. It is NOT the cause: the currently-serving `lia` is pid 53212 /
started 17:38, not the autostart instance (pid 27368). Autostart replay runs at daemon boot ONLY
(`autostart.rs:38`), so it does not clobber a running endpoint.

Eliminated this session, measured, do not re-litigate:

- **Git repo — DEAD.** Neither `liaison` nor `travelear` nor `projects` is a git repo. All three
  fall back to folder name in `project_id_for_dir`. (The earlier note recorded this as refuted for
  a different, weaker reason.)
- **Project index — DEAD.** `lia`'s index row carries all three refs (`projects`, `travelear`,
  `liaison`) with clean id/display; `gears` resolves to `travelear`. Nothing is being dropped.
- **Dup-session guard (`REQ-RUN-NO-DUP-SESSION`) — DEAD.** Zero `ENDPOINT_ALREADY_LIVE` /
  `ENDPOINT_CREATE_CONFLICT` in the entire 294 MB daemon log. That guard never fired.

Surviving correlation only: every working endpoint cwd is a git repo *except* `projects` itself;
both failing dirs are non-git. `projects` being non-git-and-working stops that from being a cause.

## The evidence

`gears` spawn, from the specimen (wall_ms):

```
1788735069384  controller-attach   conn=701013 session=25 endpoint=gears by=local
1788735069399  write-start
1788735069520  writer-exit         role=controller session=25 reason=channel-closed
1788735069536  session-detach      was_controller=true
1788735069546  RC_ESTABLISH        <- CLIENT logs success, 10 ms AFTER the detach
1788735075765  harness boot        <- 6.4 s later
```

Baseline at capture: `SUBSCRIBE_DECISION gears: 0`, `stream-subscriber gears: 0`,
`RC_ESTABLISH gears: 3`, harness pid alive. Three operator attempts over 8 minutes
(22:51:09, 22:51:52, 22:59:36), all three `RC_ESTABLISH ... session=25`, zero decisions.

**The `writer-exit channel-closed` arc is NOT the defect.** `lia`, which works, shows the identical
arc on conn 713146 (session 32). Reading that arc as the failure was wrong; the working control
refutes it.

## What actually separates working from failing

| | `lia` (works) | `gears` (fails) |
|---|---|---|
| `role=brain controller` attach → `writer-exit` → detach | yes | yes |
| second conn `role=brain stream-subscriber` | **yes** (conn 713153, stream 415079) | **never** |
| `SUBSCRIBE_DECISION` | `decision=controller` | **0, always** |

The failing half is that the **stream-subscriber seat is never installed**.

## Localization

`nethost.rs:545 begin_attach` installs the subscriber seat and is where the conn is described
`stream-subscriber stream={id}`. Its refusal path —

```rust
if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {
    return Err(io::Error::new(io::ErrorKind::WouldBlock,
        format!("stream {} subscriber busy: prior subscriber still draining", self.stream_id)));
}
```

— returns **above** the `sub.describe(...)` call. A refused install therefore emits no
`stream-subscriber` line, seats no controller, and never reaches `dispatch_subscribe`.
That is precisely the `gears` signature.

**Ordering is evidence, not inference:** `lia`'s conn is described
`role=brain stream-subscriber stream=415079 controller session=32 endpoint=lia` — the
`stream-subscriber` description is applied in `begin_attach` BEFORE the controller description,
so `begin_attach` is upstream of `dispatch_subscribe`.

Consistent with a permanently-wedged prior seat: a poisoned prior subscriber that never becomes
`gone()` refuses every later attach forever, which matches 3 attempts / 8 minutes / 0 decisions
better than any startup race does.

## ⚠ Consequence for the blind-panel bounce — READ BEFORE SPENDING IT

`fix/rc-subscribe-blind-panel` @`bd3a337b` instruments the two early returns inside
`dispatch_subscribe`. The evidence above says the `gears` failure dies **upstream of that
function**. **Prediction, falsifiable: deploying that patch and bouncing the daemon will print
nothing for `gears` and buy nothing for this defect.** The bounce costs 11 live perches. The patch
remains correct and worth landing on its own merits — it is simply not the instrument for this.

## ⚠ Two blind panels, not one

`begin_attach`'s busy refusal is silent by construction. The presence call site says so in the
source: `nethost.rs:2246` — `let _ = ...begin_attach(...); // busy refusal: quiet, as before`.
So `grep "subscriber busy"` returning **0 across the whole log proves nothing** — a refusal that is
never written cannot be counted. Same trap: `stream-sub-attach` lines carry no endpoint field, so
"0 for gears" there is a structurally uninformative zero, not evidence.

## Next

1. Instrument `begin_attach`'s WouldBlock refusal (stream id, prior poisoned/gone/is flags) — this
   is the panel that is actually dark on this path. Needs the same bounce; worth pairing with the
   blind-panel patch so one bounce lights both.
2. The rc CLIENT pump (`drive_established`) needs no bounce and is still unexercised.
3. Still missing, and only the operator can produce it: the exact command + exact output of a
   failing `lia`-in-`liaison` run.

`RC_ESTABLISH` being written 10 ms after the broker's own detach is a reporting defect in its own
right — the client declares establishment its peer has already torn down.
