Menu

#261 fix(db): self-heal migration checksums instead of crash-looping

closed
nobody
released (243)
2026-06-11
2026-06-11
Anonymous
No

Originally created by: Akarsh-Hegde

The bug

Every existing user's daemon crash-loops on upgrade to any build containing [#250]:

Caused by:
    migration 1 was previously applied but has been modified
Error: failed to run migrations

(Reported live by a user on 1.43.0 → 1.46.0 — meridian status showed "loaded but not running" while the log repeated meridian daemon starting every ~30 s as launchd restarted it.)

Root cause

sqlx records a SHA-384 checksum of each migration's bytes in _sqlx_migrations when it's applied, and re-validates every applied migration on startup. [#250] ("update file headers… tagline") rewrote line 1 of all 34 shipped migrations — only a comment, no schema — but sqlx hashes the whole file, so every checksum changed. Existing DBs hold the old checksums; the new binary ships the new files → mismatch → run() aborts → daemon panics before the poll loop.

(The error names "migration 1" only because it's the first mismatch checked; all 34 are broken — so reverting a single file just moves the error to migration 2.)

Fix — repair forward, don't freeze the bytes

This keeps [#250]'s headers. Before running migrations, setup_db now calls reconcile_migration_checksums(), which:

  1. No-ops on a fresh DB (no _sqlx_migrations table yet).
  2. For each already-applied migration whose stored checksum differs from the embedded file's checksum (migrator.iter()), rewrites the stored checksum to match and logs a warn! naming the version.
  3. Lets run() proceed and apply any genuinely new migrations as normal.

Every stuck install self-heals on its next daemon start — no revert, no user action, no migrate-db dance — and it's forward-proof against any future accidental reformat.

Trade-off (called out honestly): this weakens sqlx's tamper-detection — a genuine SQL change to a shipped migration is now accepted with a warning rather than blocked. The warn! is the audit trail, and the "never edit a shipped migration" rule remains the real guard.

Tests

  • reconcile_repairs_drifted_checksum: applies migrations, corrupts a stored checksum, asserts run() then fails (reproducing the crash), runs reconcile, asserts run() succeeds.
  • reconcile_is_noop_on_fresh_db: reconcile on a DB with no _sqlx_migrations table is a clean no-op.

Full suite green: 256 lib tests + integration, clippy -D warnings and fmt clean.

Relationship to other PRs

Supersedes the closed [#260] (which reverted the headers). This is the "keep the tagline, fix the checksums" approach instead.

https://claude.ai/code/session_013mv3epyceLahz9Cue1mtPj


Generated by Claude Code

Related

Tickets: #250
Tickets: #260
Tickets: #262

Discussion

  • Anonymous

    Anonymous - 2026-06-11

    Ticket changed by: Akarsh-Hegde

    • status: open --> closed
     
  • Anonymous

    Anonymous - 2026-06-11

    Originally posted by: adityaharishch

    🎉 This PR is included in version 1.48.1 🎉

    The release is available on:

    Your semantic-release bot 📦🚀

     

Log in to post a comment.