lobehub/lobehub · critical · Error
Local database migration ${applied.version} (${applied.name}
Error message
Local database migration ${applied.version} (${applied.name}) differs from the application manifest What it means
Migration runner detects that an already-applied migration's recorded name or sha256 checksum does not match the application's current manifest entry for the same version. This guards against silent edits to a released migration (which would corrupt the audit trail) and against a manifest whose statements were rewritten after deployment.
Source
Thrown at apps/desktop/src/main/database/migrations/runner.ts:74
for (const [index, applied] of appliedMigrations.entries()) {
const expectedVersion = index + 1;
if (applied.version !== expectedVersion) {
throw new Error(
`Local database migration history is not contiguous: expected version ${expectedVersion}, received ${applied.version}`,
);
}
const migration = migrations[applied.version - 1];
if (!migration) {
throw new Error(
`Local database version ${applied.version} is newer than this application supports`,
);
}
if (applied.name !== migration.name || applied.checksum !== migrationChecksum(migration)) {
throw new Error(
`Local database migration ${applied.version} (${applied.name}) differs from the application manifest`,
);
}
}
};
const applyMigration = (database: DatabaseSync, migration: LocalDatabaseMigration) => {
database.exec('BEGIN IMMEDIATE');
try {
for (const statement of migration.statements) database.exec(statement);
database
.prepare(
`INSERT INTO ${MIGRATIONS_TABLE} (version, name, checksum, applied_at) VALUES (?, ?, ?, ?)`,
)
.run(migration.version, migration.name, migrationChecksum(migration), Date.now());
database.exec('COMMIT');View on GitHub (pinned to 10f24d7ade)
Solutions
- Revert the edit to the migration file so its name + statements match the version the DB recorded — this is the correct fix in 99% of cases.
- If the edit is intentional and required, ship it as a NEW migration (next version) instead of editing the old one, so checksums stay stable.
- If the DB itself is wrong (e.g. you know the applied migration is corrupt), back it up and repair the __local_database_migrations row with the correct name + checksum — only as a last resort.
- Add a pre-commit hook that fails if migration files older than the latest version are modified.
- Run the runner with logging that prints applied vs manifest checksum side-by-side to confirm the diagnosis.
Example fix
// before
if (applied.name !== migration.name || applied.checksum !== migrationChecksum(migration)) {
throw new Error(`Local database migration ${applied.version} (${applied.name}) differs from the application manifest`);
}
// after — show which field diverged and both checksums for diagnosis
const manifestChecksum = migrationChecksum(migration);
if (applied.name !== migration.name || applied.checksum !== manifestChecksum) {
throw new Error(
`Local database migration ${applied.version} differs from manifest: ` +
`name applied='${applied.name}' manifest='${migration.name}'; ` +
`checksum applied=${applied.checksum} manifest=${manifestChecksum}`,
);
} Defensive patterns
Strategy: validation
Validate before calling
import { createHash } from 'node:crypto';
function manifestChecksumMatches(applied: { name: string; checksum: string }, migration: { version: number; name: string; statements: unknown[] }): boolean {
const expected = createHash('sha256').update(JSON.stringify([migration.version, migration.name, migration.statements])).digest('hex');
return applied.name === migration.name && applied.checksum === expected;
} Type guard
function isAppliedMigration(value: unknown): value is { version: number; name: string; checksum: string } {
return typeof value === 'object' && value !== null
&& typeof (value as any).version === 'number'
&& typeof (value as any).name === 'string'
&& typeof (value as any).checksum === 'string';
} Try / catch
try {
runLocalDatabaseMigrations(database);
} catch (e) {
if (e instanceof Error && /differs from the application manifest/.test(e.message)) {
// a migration file was edited after release — revert the file or add a new migration instead
showMigrationChecksumMismatchDialog();
app.quit();
} else throw e;
} Prevention
- Never edit a released migration's statements or name — add a new migration instead.
- Add a pre-commit hook that fails when files older than the latest migration are modified.
- Treat the checksum column as immutable for already-applied versions.
- Log applied vs manifest checksums side-by-side on startup to catch divergence early.
When it happens
Trigger: A developer edits statements of an already-shipped migration after users have applied it; the migration's `name` field was renamed; the migration order was reshuffled but the version numbers kept contiguous; the DB was migrated by a different fork whose migration shared a version number but different SQL.
Common situations: Rebasing a feature branch that touched migration files; a cherry-pick that introduced conflicting migration edits; a manual SQL hotfix that did not update the checksum; a fork/divergent build writing to the same DB.
Related errors
- Local database version ${applied.version} is newer than this
- Failed to attach image(s) to Codex app-server: ${this.getErr
- ${trackedResult.stderr || 'git ls-files failed'}
- Invalid proxy configuration: ${validation.errors.join(', ')}
- ${result.message || 'Connection test failed'}
AI-assisted analysis of lobehub/lobehub@10f24d7ade (2026-08-12).
Data as JSON: /api/errors/656b98c79d9f0d04.
Report an issue: GitHub.