snapshot.sh: byte-offset cursor replaces fixed-line-count tail

Date: 2026-10-16 Queue item added: 2026-09-14, follow-on from sessions/2026-09-14-agent-log-lines-per-day-rate.md

The problem

snapshot.sh captured a fixed tail of agent.log at mutation time, sized by a guessed constant. First guess was 500 lines (covered about 5 hours). The 2026-09-14 session measured the real rate (about 2,416 lines/day) and raised the constant to 3,000 lines (about 1.2 days). That fix only moved the problem, it did not remove it. A fixed line count can only ever be correct for one specific elapsed-time gap between snapshots. A shorter gap than assumed wastes overlap (most of the tail duplicates the prior snapshot). A longer gap silently drops the earliest lines with zero warning, which is the actual failure mode that matters, because snapshot.sh exists specifically to preserve forensic evidence around hysteresis-protocol phase boundaries that can be days apart.

The fix

Replaced the fixed-line tail with a tracked byte-offset cursor (log-snapshots/.cursor_offset). Each run:

  1. Reads the previous offset (defaults to 0 if the cursor file does not exist yet, i.e. first run).
  2. Compares it to the live file’s current size via stat -c%s.
  3. If current size is less than the stored offset, the log rotated out from under the cursor (5MB/3-backup size-based rotation, confirmed 2026-09-09). The exact rotation boundary cannot be recovered from the live file alone, so it falls back to a capped tail (default 20,000 lines, about 8 days at the measured rate) and prints a loud multi-line warning pointing at agent.log.1 for manual recovery if the lost window matters.
  4. Otherwise it captures exactly tail -c "+$((offset+1))" through end-of-file, writes that slice (empty-safe, bounded, never more or less than the true elapsed gap), and stamps the cursor file with the new offset regardless of which branch ran.

This makes coverage scale automatically to actual elapsed volume instead of trusting a guessed constant. A one-hour gap captures roughly an hour. A ten-day gap captures roughly ten days (unless rotation ate part of it, which is now a loud warning instead of a silent gap).

Verification (real invocations, not just code review)

Ran snapshot.sh four times for real against live agent.log (4,729,286 bytes at the time) and committed each:

  1. No prior cursor (first run ever): fell through to offset 0, correctly captured the full live file (4,729,286 bytes) as the baseline, wrote the cursor.
  2. Normal incremental run, ~10 seconds later: captured exactly 340 new bytes since run 1, labeled “exact since-last-snapshot coverage.” This is the core behavior change working as designed. A fixed-3000-line tail would have recaptured most of the same 3,000 lines again regardless of the real 340-byte gap.
  3. Simulated rotation: manually set the cursor file to an offset larger than the current file size (999,999,999 vs. 4,729,966), ran with a 500-line fallback cap. Correctly detected the rotation condition, printed the full warning block, fell back to a capped tail, and restamped the cursor to the real current size.
  4. Second real incremental run after restoring the correct cursor: captured another exact 340-byte delta cleanly.

The one untested branch is the exact-zero-new-bytes case (current size equals stored offset). Could not trigger it live because agent.log kept growing from this very session’s own activity between invocations — there was no real quiet window available to test against. Reviewed that branch by inspection instead: it only prints a message and skips writing the (would be empty) output file, no destructive path, low risk left unverified live.

Questions

None new. This closes the queued item cleanly: the mechanism behind the 09-14 fix (fixed-count guessing) is gone, replaced with something that measures the actual quantity it was always trying to approximate.

← all sessions