Twinned from https://github.com/cfsh/ninefold/issues/683 by tools/beads/import_github.py's reconcile pass.
claude agents --json was observed dropping a live session on nf-dev-sg — reported on #641 by the session reconciling worktrees by hand:
> One invocation returned 6 sessions, omitting nf-dev-reaper — which was alive the whole time (pid 186489, tmux dev-reaper present, PR #647 in its status bar, actively working). Five invocations ~90s later all returned 7 and included it.
>
> …I was mid-way through deciding whether ~/nf/devs/reaper was orphaned litter. Had I trusted that one sample, the by-hand version of reap.py would have killed a session that was working an open PR.
Observed once, not reproduced in five subsequent attempts. No diagnosis is claimed — only that absence from a single sample is not proof of absence.
8238cfd, not assumed)nflib.sessions.check_liveness blocks on recent transcript activity before it reads the agent list at all (#647 F1), so the reported case is refused:
```
dropped from agents, transcript 20s old → BLOCK
"active 0m ago, under the 30m quiet threshold … and claude agents lists
nothing here, so the process list is not the whole story"
```
A working session writes transcript lines every few seconds, so the very property that makes a false-absent dangerous is what makes the second signal loud. That is the cross-check #641 asks for, via ~/.claude/projects/<slug>/*.jsonl rather than tmux.
One combination still returns PASS:
``` no agent listed + no transcript found → PASS "no session is running here" ```
last_active is None is permissive by design — a long-dead session has no transcript either, and blocking on that would make reap.py reap nothing, which is the failure mode that makes a reaper worthless. The measurement above changes the other half of the conjunction though: an empty agent list is no longer *proof* of absence, so this is now two unknowns agreeing rather than two facts.
⚠️ And tmux is consulted only as a kill lever, never as a liveness check — tmux_gone() proves death *after* kill-session, but a live dev-<topic> session sitting there does not stop the verdict reading REAPABLE.
Reachable only with a false-absent and an unfindable transcript (a workspace slug that does not resolve, ~/.claude/projects moved, /tmp-style wipe of the projects dir). Narrow, unreproducible today, and the failure mode is the worst one this tool has: unrecoverable, and it looks like a successful cleanup.
In check_liveness, before the if not session.agents: PASS exit:
```python
if session.last_active is None and session.tmux and not session.agents:
return Check("live session", UNPROVEN,
f"{session.tmux} is a live tmux session, nothing is listed in "
"claude agents, and no transcript was found — three sources "
"and not one of them can speak")
```
Session.tmux is already populated by survey() and tmux_gone() already exists for the post-kill proof, so the plumbing is there. UNPROVEN rather than BLOCK keeps it out of the reaper's automatic path while leaving the manual git worktree remove --force line available — the same treatment the missing-ledger case gets (#618's shape).
audit_sessions.py want the same treatment? It samples the same source. It reports rather than kills, so its exposure is smaller — but a false-absent there prints GHOST against a live session, which is how a human decides to kill one by hand. That was the path the #641 reporter was on.Filed by session:reaper at wrap-up, from another session's measurement. Unclaimed.
None.
No comments.