---
name: stopped-local-ssh-does-not-stop-its-remote-command
description: "TaskStop on a local ssh leaves the remote bash alive (no tty); relaunching the same chain then runs two full batteries concurrently in one pool and one output dir, forging load reds and a two-summary nextest.raw"
metadata: 
  node_type: memory
  type: project
  originSessionId: 6c1b8106-fa83-4fff-8e19-385f80067f8b
  modified: 2026-09-06T19:17:58.476Z
---

2026-09-06 18:24Z-18:50Z, kitsubito, W0 head 0b9554b3 (todlando's rig, my triage). He launched a
background `ssh kitsubito 'wait-for-old-driver → reset → driver → smoke'`, saw it would outlive
the local 10-min ssh cap, TaskStop'ped it, and relaunched the same chain detached with
`setsid nohup`. The stopped ssh's REMOTE bash did not die (no tty; it was sleeping in its wait
loop) — both chains woke when the old driver exited and ran TWO full `cargo nextest` suites over
ONE target dir and ONE `nextest.raw` (both opened `wb`, interleaved: one `Starting 2776 tests`
header, two `Summary` lines 1123 s / 1137 s, different fail sets and test numbering), two
smokes, one docs port. Load avg 15.3, `DOCS_SERVER_BIND_FAIL: 5474 in use`, and three
"load" reds (resident_service precondition ×2, ring_blocks, resume_no_control_steal) — every one
of them manufactured by the rig. The FIRST map I received named "load, exe-hash ready path" as
the mechanism and cited the 1-failed summary only; the second summary was 60 lines further down.

**Why:** a live-lane pool share (releases#103 class) reached through a process-lifetime gap
rather than through a path; nothing in the pool guard sees two nextests of the SAME lane. And
the driver's log grep (`sort -u | head -8`) showed one Summary, so the driver's own report
hid the second run.

Pairing CORRECTED by hertz 19:17Z: both summaries were 2776-test runs = both at HEAD (the
earlier gate-am2 run was 2771 tests, green, and the chain provably waited for it). HEAD-vs-HEAD,
never old-vs-new — a guard aimed at "old battery vs new battery" would not have caught it.

**How to apply:** (1) never trust a nextest.raw with two `Summary` lines — `grep -c Summary`
before reading any FAIL line; two = void, not red. (2) A stopped local ssh is not a stopped
remote: kill by REMOTE pid, or make the remote side idempotent. (3) The lock guards the POOL,
so it lives at the WORKTREE level (one fixed path per worktree, taken by every driver
invocation regardless of output dir) — a lock on `$OUT` lets a relaunch with a different
outdir share target/ and the port uncontended (hertz, same day; ruled). (4) When a red arrives
with a named LOAD mechanism, ask what ELSE was on the box before accepting — self-inflicted
load is a rig defect wearing a ledger row's name. See [[open-the-subindex-before-running-a-gate]]
and the pool rules in AGENTS.md.

**AMENDMENT (hertz, 2026-09-06, measured on the rig itself) — the lockfile fix in (2) is
scoped WRONG, and following it as written walks back into this.** `w0-head-chain.sh` (mtime
12:14:50, i.e. added AFTER the 11:50 collision; gate-head/ has no `.lock`, gate-head2/ does)
guards with `exec 9>"$OUT/.lock"; flock -n 9`. But `$OUT` is an ARGUMENT — `OUTDIR="${3:-.spt/gate-head}"` —
so it locks the OUTPUT DIR, while the thing actually shared is the worktree: one `target/`, one
port 5474, one checkout the chain `git reset --hard`s. Two chains launched with different
outdirs (exactly the gate-head vs gate-head2 shape) each take an UNCONTENDED lock and collide
anyway, silently, with no REFUSED line to read afterwards. The lock must sit on the POOL
(worktree/target), not on `$OUT`; the file's own point (3) is the right instinct and (2) as
written contradicts it. A guard whose scope is narrower than the resource is worse than none:
it prints reassurance.

**Also measured, against the pairing that was first suspected:** the concurrent pair was
HEAD-vs-HEAD, never old-battery-vs-new. The am2 battery was 2771 tests / 0 failed / 1049.333 s;
BOTH summaries in the poisoned `nextest.raw` are 2776-test runs. The test COUNT is the cheap sha
discriminator when two runs share a file — use it before reasoning about which drivers collided,
because the serialization that was supposed to prevent this DID work (`old-driver-gone capped=0`,
and the chain logged am2's summary before resetting). The guard that failed was not the one
everybody looked at first.
