Twinned from https://github.com/cfsh/ninefold/issues/690 by tools/beads/import_github.py's reconcile pass.
Unclaimed and unlabelled — inert backlog, per CLAUDE.md. Same shape as #559 and #658.
There are now three implementations of "is this pid running?", and they do not agree on the question's one dangerous case:
| where | signature | Windows |
|---|---|---|
| tools/nflib/sessions.py:pid_alive | -> bool | ⚠ unguarded — calls os.kill(pid, 0) on any platform |
| tools/reviewer/dispatch.py:_pid_alive | -> bool \| None | refuses to ask (os.name != "nt"), with the full write-up |
| tools/pr/rebuild_f5.py:pid_alive | -> bool \| None | refuses to ask; added by #689 for design 004's publish lock |
⚠ os.kill(pid, 0) is not a probe on native Windows — it TERMINATES. CPython special-cases only CTRL_C_EVENT/CTRL_BREAK_EVENT; every other signal opens the target and calls TerminateProcess(handle, sig), and signal 0 is "every other signal". The victim exits with code 0, indistinguishable from a clean exit in any log.
So the divergence is not cosmetic. nflib.sessions.pid_alive is the one in the shared library, it is the one a fourth caller would reach for first, and it is the one that would kill a stranger's process on the sandbox box. It is safe today only because its sole caller (reap.py) is tmux-gated and therefore Linux-only — a property of the caller, not of the helper.
design 004 §6 is explicit that the S1 chunk should not grow an nflib helper — *"One caller does not justify it, and #559 is the standing evidence that premature sharing here means editing three tools on the merge path."* That argument is about the lock; it applies to this too. rebuild_f5.py and dispatch.py are both on the merge path and the reviewer path respectively, and reconciling nflib.sessions.pid_alive's two-valued contract with the other two's three-valued one is a behaviour change to reap.py, not a move.
One nflib home with the three-valued contract (bool | None, None = "cannot be asked safely") and the probe_safe test seam, then three call sites updated. reap.py is the one that needs a decision rather than a substitution: it currently treats the answer as a bare bool, and None has to mean something there — most likely "leave it alone", matching how dispatch.py falls back to an age gate.
The probe_safe seam should come along. Its reason is not obvious and is recorded in dispatch.py: patching os.name in a test changes it for pathlib too, and Path.home() starts resolving through Windows rules mid-suite.
None.
No comments.