Twinned from https://github.com/cfsh/ninefold/issues/540 by tools/beads/import_github.py's reconcile pass.
poll.py --check advances IDLE_TICKS on every invocation, and the cadence ladder reads that counter as elapsed quiet time. Those are the same number only if --check runs exactly once per cron tick, which nothing enforces and nothing documents as a precondition.
emit_idle's comment is explicit that this is deliberate:
> ⚠ --check OWNS the counter; the full run only ever RESETS it.
That is the right fix for the double-count #414 pre-registered (check-then-full counting one tick twice). The assumption underneath it — one --check per wall-clock interval — is the part nothing holds up.
A poll tick returned CHANGED and then an HTTP 504 appeared mid-digest. To decide whether the digest had silently degraded, I re-ran --check twice. That is the obviously correct diagnostic move, and it is free by design — the probe is ~5s and reads nothing.
Result: IDLE_TICKS went 0 → 1 → 2 in under a minute, and the next digest printed
POLL_CADENCE: 17 * * * * (hourly) — recreate the cron if yours differs.
So a transient GitHub 504, plus the correct response to it, backed the session's cron off to hourly on the basis of ~60 seconds of quiet. With #530 sitting CONVERGED and awaiting an approval, that is up to an hour of added latency on the one event the loop exists to catch.
Then, correcting the state file by hand, I ran --check once more to verify the correction — which re-incremented it. The counter cannot be inspected without being mutated, so there is no read-only way to answer "what does the ladder currently think?"
The skew is one-directional: extra invocations only ever make the loop quieter. Every other cause is the same shape —
RUN-FULL on a quiet tick, if a future edit ever re-adds the full-run increment #414 removedand CLAUDE.md's own framing of the cost is asymmetric in the other direction: *"#360 sat approved-pending for ~21 hours with PRs stacked behind it"* is the failure the ladder exists to bound, and backing off early makes that class worse, not better. The emit_idle comment calls sooner-backoff "the safe direction", which is true for token cost and false for latency; the ladder is supposed to trade those against each other, and an invocation-counted ladder gets the trade wrong in a way nobody can see.
Store a timestamp alongside the fingerprint and refuse to advance on a call that arrives well inside the current interval:
session:c|2156978180 2 2026-08-04T10:41:07Z <- new third line
--check advances only if now - last_advance >= ~0.5 × current_cadence; otherwise it reports the existing count unchanged. Out-of-band probes then become genuinely free, which is what they are already assumed to be, and the ladder measures the thing it is named for.
Two cheaper alternatives if that is more machinery than wanted:
--check --peek — report without mutating. Solves diagnosis and the can't-look-without-changing-it problem, not cron double-fires.--check that follows another within N seconds — cruder, no timestamp semantics to get wrong, but throws away a real quiet tick when a diagnosis happens to follow one.The state file already versions cleanly (State.read handles a bare legacy fingerprint and a scope tag), so a third line is a compatible addition rather than a migration.
Related: #414 (pre-registered the double-count this counter already fixes), #419 (replacing the cron with Monitor, which removes the ladder entirely — if that lands, this is moot, which is an argument for the cheap fix rather than the thorough one).
Filed unclaimed.
None.
No comments.