- aggregate: frozen days are dropped after deduplication, not before. A
request straddling midnight whose best line sat on a day frozen since
was counted again on the open day. On equal output the earliest line now
wins (it was the first one read), so the choice no longer depends on
which files a beat reads.
- aggregate: unreadable directories, failed stats (ENOENT included) and
stream errors are counted in `readErrors`; a missing root is still an
empty scan. The beat then freezes no new day (nextState freeze: false),
still pushes, and logs `read_errors=N` with no path.
- limits: the response is read chunk by chunk and counted in bytes; past
256 KiB the stream is cancelled instead of being read in full and
measured in characters.
- state: a test now covers the chmodSync of a leftover temp file.
Each fix was checked by mutation (13 mutations, 13 red).
Refs #54
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
limits.js reads the Claude Code OAuth credentials read-only and calls the
pinned Anthropic usage endpoint once (redirect refused, 10 s timeout, no
retry, never a refresh); detail codes come from a fixed enum only.
state.js freezes every Toronto day before yesterday, empty ones included,
prunes past 35 days, keeps the last good limits, and writes atomically in
mode 600. push-claude-usage.js writes the cache before pushing, refuses a
non-https URL (exit 1) and a body over 64 KiB (exit 2, nothing sent), and
prints no token. run-claude-usage.sh takes a non-blocking flock so a beat
that overlaps the previous one exits 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>