Twinned from https://github.com/cfsh/ninefold/issues/618 by tools/beads/import_github.py's reconcile pass.
A missing tracked-prs.txt is indistinguishable from an empty one, and the difference is the difference between "my chunk is done" and "I have lost track of a live PR".
Hit for real on 2026-08-06. My session's scratchpad under /tmp was wiped at 08:49 UTC while PR #603 was open, unmerged and awaiting approval. The next tick's digest opened with:
``` ════ TRACKED PRS ════ (no tracked file at /tmp/.../scratchpad/tracked-prs.txt) ```
I noticed. The concern is the paths where nobody would.
tracked_numbers() returns [] for a missing file — the same value as for an empty one:
```python def tracked_numbers(tracked: Path) -> list[str]: if not tracked.is_file(): return [] ```
Three things follow, in increasing severity:
1. --check never mentions it at all. emit_tracked_prs — the only thing that prints (no tracked file at …) — is called from run_digest, not run_check. CLAUDE.md tells every session to run --check first, every tick, precisely because it is ~5s against ~2min. On a quiet tick that path prints QUIET and nothing else. The cheap path that runs every tick is silent about having lost its subject.
2. The GAPS-ledger wake becomes unreachable. run_check's quiet path iterates tracked_numbers(tracked) to find unactioned ledgers. With no file it iterates nothing, so WAKE: N unactioned GAPS ledger(s) can never fire — and that wake exists specifically because "a ledger never perturbs the fingerprint, so quiet is where it hides". Losing the file makes the hiding place permanent.
3. WRAP_UP can fire with a live PR outstanding. The predicate is tracked_open == 0 and not mine. A wiped file makes tracked_open == 0 honestly-looking, so the only thing standing between a session and a false "the chunk is done, delete the cron, set your row to wrapped" is whether it also happens to have a claimed issue open. Mine did. A session whose issue closed first would have been told to wrap with an unmerged PR sitting in the queue.
emit_idle already guards the mirror-image case, and correctly:
``` PREDICATE UNEVALUABLE: no sid given, so claimed issues could not be checked and WRAP_UP is suppressed. ```
That is exactly the right move — refuse to evaluate rather than evaluate on a degraded input — and the missing tracked file is the same hazard on the other half of the same and. It has no guard.
tracked_numbers returning [] for two different situations is the root. Distinguishing absent from empty at that call site would let:
run_check print the absence on every tick, cheaply — it already has the Path;emit_idle suppress WRAP_UP with PREDICATE UNEVALUABLE, matching the sid guard;⚠️ Worth keeping the two apart deliberately: emptying tracked-prs.txt is a documented wrap-up step, so "empty" must stay a valid path to WRAP_UP. Only *absent* is the ambiguous one.
The file lives in a session scratchpad under /tmp, which is not durable — that is what wiped here. Whether the poll's state belongs somewhere that survives a /tmp clean is a larger question than this issue; this one is only about the tool not degrading silently when it is gone.
None.
No comments.