The guard that watched 3 of 11 writers

A SessionStart hook exists specifically to catch analytics writers that die silently. It ran every session for four months and reported green, while four files under ~/.claude/analytics/ had not been written since April. Its watch list named three files. All three were alive.

Toggle the watch list

filelast writewatched?guard verdict

Why it could not see them

The guard is a peer comparison: a file is dead if it is stale (48h+) while a sibling is fresh (24h-). That logic is correct and was never the problem. The watch list was the problem. It was written when three writers existed, and every writer added later was invisible to it by construction, including all four that died.

BEFORE                                   AFTER
const WATCHED = [                        readdirSync(dir)
  'skill-usage.jsonl',    <- alive         .filter(f => f.endsWith('.jsonl'))
  'agent-usage.jsonl',    <- alive         .filter(not a rotated archive)
  'hook-timing.jsonl',    <- alive         .filter(not event-driven)
]                                          .filter(not deliberately retired)

sees 3 of 11. the 8 it cannot         sees every writer by default.
see include all 4 that died.          a new writer is covered without
                                      anyone remembering a list.

The root cause chain

  1. #1266 (v7.30.0, April) removed the appendAnalytics calls from cache-break-detector and sync-session-dispatcher, stating the data was "already in the emit path".
  2. That path did not carry it. Neither file calls emit() at all, and emit() fans out to sinks that write ~/.claude/ork-telemetry/, not ~/.claude/analytics/. That directory holds one file today: circuit.json. Meanwhile appendAnalytics describes itself as the source of truth.
  3. The hooks kept running. cache-break-detector still computed every shape delta each turn and then discarded it, which is why restoring the write is three lines rather than a rebuild.
  4. dx-signals was never real. A tree-wide search finds that name only in the forwarder's allowlist and its own test. It advertised an event stream nothing has ever produced.
  5. The guard reported green throughout, because of the watch list above.

A claim this PR deliberately refutes

The issue reported cache-break-detector as an orphan: zero references in hooks.json, zero in the entries map, therefore it "cannot ever have fired." Counting is right; the conclusion is not. It is fanned out by prompt/unified-dispatcher, which is registered in both surfaces, so a dispatched hook has zero direct references by design. It fires every turn, proven by 19 live state files it wrote and by its minified body sitting in the shipped bundle. Acting on the original reading would have been the actual regression: re-registering it double-fires it and double-counts /goal turns, and deleting it breaks turn counting outright.