Skip to content

Add error handling for outdated clients running against newer DB schemas#741

Merged
RhysSullivan merged 4 commits intoRhysSullivan:mainfrom
grfwings:schema-migration-handling
May 10, 2026
Merged

Add error handling for outdated clients running against newer DB schemas#741
RhysSullivan merged 4 commits intoRhysSullivan:mainfrom
grfwings:schema-migration-handling

Conversation

@grfwings
Copy link
Copy Markdown
Contributor

@grfwings grfwings commented May 10, 2026

Context

A user can currently hit an opaque SQLite error when switching between Executor builds that know different local DB schemas:

  1. Run a release executor binary against EXECUTOR_DATA_DIR.
  2. Run a newer/dev build (bun dev:cli) against the same EXECUTOR_DATA_DIR, which applies newer local Drizzle migrations.
  3. Run the older release binary again.
  4. Startup fails later with a low-level error such as no such column: invocation_config.

Changes

Add a read-only/synchronous preflight in local executor startup, immediately before migrate(db, { migrationsFolder: MIGRATIONS_FOLDER }).

The preflight will:

  1. Check whether __drizzle_migrations exists.
    • If missing, treat the DB as fresh for this purpose and let Drizzle initialize/migrate it.
    • This intentionally follows the requested small-scope behavior; existing untracked legacy DB handling remains separate (moveAsidePreScopeDb, readLegacySecrets).
  2. If __drizzle_migrations exists, read applied hashes ordered by id ASC.
    • id is autoincrement, monotonic, non-null, and avoids timestamp tie issues.
  3. Compute bundled migration hashes exactly like Drizzle:
    • Read meta/_journal.json.
    • Sort entries by idx.
    • For each entry, read ${entry.tag}.sql as raw text.
    • Hash the entire raw SQL file with SHA-256 before statement-breakpoint splitting.
  4. Compare applied migration history against the bundled migration history as a prefix.
    • If applied count is greater than bundled count: throw LocalDatabaseSchemaTooNew.
    • If any applied hash does not equal the bundled hash at the same index: throw LocalDatabaseMigrationHistoryMismatch.
    • If __drizzle_migrations exists but is empty: pass and let Drizzle run all migrations.

Two new errors:

  • LocalDatabaseSchemaTooNew: likely opened by a newer Executor; user action is to update Executor or choose another EXECUTOR_DATA_DIR.
  • LocalDatabaseMigrationHistoryMismatch: DB history does not match this binary's migration lineage; likely different branch/corruption/manual modification; user probably cannot fix this by merely updating to the same release line.

@grfwings grfwings force-pushed the schema-migration-handling branch from 6dcd5e4 to ddaab4b Compare May 10, 2026 04:02
- Use loose != null in drizzleMigrationsTableExists so undefined rows
  from bun:sqlite's .get() are treated as missing.
- Plumb the resolved dataDir through checkDrizzleMigrationCompatibility
  instead of re-reading process.env.EXECUTOR_DATA_DIR at error-format
  time, keeping the message aligned with resolveDbPath().
@RhysSullivan RhysSullivan merged commit 095b698 into RhysSullivan:main May 10, 2026
6 checks passed
@RhysSullivan RhysSullivan mentioned this pull request May 10, 2026
3 tasks
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.

2 participants