Skip to content

fix: recurring hourly sentinel.db backup - #508

Open
essentialbit wants to merge 11 commits into
mainfrom
fix/db-backup-recurring-20260725
Open

fix: recurring hourly sentinel.db backup#508
essentialbit wants to merge 11 commits into
mainfrom
fix/db-backup-recurring-20260725

Conversation

@essentialbit

Copy link
Copy Markdown
Owner

Summary

  • Closes the open item flagged in memory since 2026-07-20: data/sentinel.db had no automated recurring backup, only a manual/sporadic SSD mirror — which was 2 days stale both times it was actually needed for recovery (2026-07-13 self-symlink incident, 2026-07-20 stale-worktree-snippet incident), permanently losing agent_track_record/feature_backlog history each time.
  • New db_backup.py: uses sqlite3's online backup API (Connection.backup()), which is transactionally consistent even while main.py's live process holds the DB open — not a raw file copy. Writes to the SSD mirror (/Volumes/Iron 1TBSSD/Claude/FredAI/data/backups) when mounted, else falls back to a local data/backups/ dir (already gitignored via the existing blanket data/ rule). Prunes to the last 48 snapshots (2 days at hourly cadence).
  • Wired as a new hourly APScheduler job (job_db_backup, jitter=60), same pattern as every other job in main.py.

Verification

  • python3 -c "from main import *; print('Import OK')" — clean.
  • Scratch-DB test (never the live DB): copied real sentinel.db to a temp dir, monkeypatched DB_PATH/backup dirs, ran backup_db() 5x, confirmed pruning keeps exactly KEEP_LAST_N, confirmed the resulting snapshot is a real openable SQLite file with the same table set as the source.
  • Caught a real bug in verification: the initial timestamp format (%Y%m%d-%H%M%S, second resolution) let same-second backup calls collide on filename and silently overwrite each other — 5 calls produced 1 file instead of 5. Fixed by adding microsecond resolution (%f) to the filename timestamp before shipping. Harmless at the real hourly cadence, but a real robustness gap against a manual retrigger.

Test plan

  • from main import * imports cleanly with the new job wired in
  • Scratch-DB backup/prune/integrity test passes
  • User to confirm the SSD mirror path is the intended destination when mounted

🤖 Generated with Claude Code

Saifodius and others added 2 commits July 25, 2026 02:52
…ata-loss incidents)

data/sentinel.db had no automated backup -- the SSD mirror was manual/sporadic
and was 2 days stale both times it was actually needed for recovery
(2026-07-13, 2026-07-20), costing agent_track_record history and proposal
rows neither time recoverable. db_backup.py uses sqlite3's online backup API
(consistent even while main.py's live process holds the DB open), writes to
the SSD mirror when mounted else a local data/backups/ fallback, prunes to
the last 48 (2 days at hourly cadence). Wired as a new hourly APScheduler job.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant