{"record":{"id":"656b98c79d9f0d04","repo":"lobehub/lobehub","slug":"local-database-migration-applied-version-app","errorCode":null,"errorMessage":"Local database migration ${applied.version} (${applied.name}) differs from the application manifest","messagePattern":"Local database migration (.+?) \\((.+?)\\) differs from the application manifest","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"apps/desktop/src/main/database/migrations/runner.ts","lineNumber":74,"sourceCode":"  for (const [index, applied] of appliedMigrations.entries()) {\n    const expectedVersion = index + 1;\n\n    if (applied.version !== expectedVersion) {\n      throw new Error(\n        `Local database migration history is not contiguous: expected version ${expectedVersion}, received ${applied.version}`,\n      );\n    }\n\n    const migration = migrations[applied.version - 1];\n\n    if (!migration) {\n      throw new Error(\n        `Local database version ${applied.version} is newer than this application supports`,\n      );\n    }\n\n    if (applied.name !== migration.name || applied.checksum !== migrationChecksum(migration)) {\n      throw new Error(\n        `Local database migration ${applied.version} (${applied.name}) differs from the application manifest`,\n      );\n    }\n  }\n};\n\nconst applyMigration = (database: DatabaseSync, migration: LocalDatabaseMigration) => {\n  database.exec('BEGIN IMMEDIATE');\n\n  try {\n    for (const statement of migration.statements) database.exec(statement);\n\n    database\n      .prepare(\n        `INSERT INTO ${MIGRATIONS_TABLE} (version, name, checksum, applied_at) VALUES (?, ?, ?, ?)`,\n      )\n      .run(migration.version, migration.name, migrationChecksum(migration), Date.now());\n    database.exec('COMMIT');","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/lobehub/lobehub/blob/10f24d7ade75139093a9373b364f6bc91f3cd7db/apps/desktop/src/main/database/migrations/runner.ts#L56-L92","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif (applied.name !== migration.name || applied.checksum !== migrationChecksum(migration)) {\n  throw new Error(`Local database migration ${applied.version} (${applied.name}) differs from the application manifest`);\n}\n\n// after — show which field diverged and both checksums for diagnosis\nconst manifestChecksum = migrationChecksum(migration);\nif (applied.name !== migration.name || applied.checksum !== manifestChecksum) {\n  throw new Error(\n    `Local database migration ${applied.version} differs from manifest: ` +\n    `name applied='${applied.name}' manifest='${migration.name}'; ` +\n    `checksum applied=${applied.checksum} manifest=${manifestChecksum}`,\n  );\n}","handlingStrategy":"validation","validationCode":"import { createHash } from 'node:crypto';\n\nfunction manifestChecksumMatches(applied: { name: string; checksum: string }, migration: { version: number; name: string; statements: unknown[] }): boolean {\n  const expected = createHash('sha256').update(JSON.stringify([migration.version, migration.name, migration.statements])).digest('hex');\n  return applied.name === migration.name && applied.checksum === expected;\n}","typeGuard":"function isAppliedMigration(value: unknown): value is { version: number; name: string; checksum: string } {\n  return typeof value === 'object' && value !== null\n    && typeof (value as any).version === 'number'\n    && typeof (value as any).name === 'string'\n    && typeof (value as any).checksum === 'string';\n}","tryCatchPattern":"try {\n  runLocalDatabaseMigrations(database);\n} catch (e) {\n  if (e instanceof Error && /differs from the application manifest/.test(e.message)) {\n    // a migration file was edited after release — revert the file or add a new migration instead\n    showMigrationChecksumMismatchDialog();\n    app.quit();\n  } else throw e;\n}","preventionTips":["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."],"tags":["database","migration","checksum","integrity","sqlite","desktop"],"backgroundTag":null,"analyzedSha":"10f24d7ade75139093a9373b364f6bc91f3cd7db","analyzedAt":"2026-08-12T11:43:19.543Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}