usebruno/bruno · critical · Error
Migration "${migration.name}" (sequence ${migration.sequence
Error message
Migration "${migration.name}" (sequence ${migration.sequence}) does not match the migration already applied to the database. It may have been modified after being applied. What it means
Thrown by _applyPending when a migration whose sequence number is already recorded in _migrations has up_hash or down_hash that differs from the current migration file's hash. The library hashes the up and down SQL with sha256 and stores both, so any post-apply edit to a migration's SQL is detected and rejected to preserve database/migration history integrity. There is no automatic recovery; the database is left as-is and the constructor closes the handle.
Source
Thrown at packages/bruno-sqlite/src/node/db.ts:73
}
_applyPending(db: DatabaseSync, migrations: Migration[]): void {
const appliedRows = db
.prepare(`SELECT sequence, up_hash, down_hash FROM _migrations`)
.all() as { sequence: number; up_hash: string; down_hash: string }[];
const applied = new Map(appliedRows.map((row) => [row.sequence, row] as const));
const insertStmt = db.prepare(
`INSERT INTO _migrations (sequence, name, up, down, up_hash, down_hash) VALUES (?, ?, ?, ?, ?, ?)`
);
for (const migration of migrations) {
const upHash = this._hash(migration.up);
const downHash = this._hash(migration.down);
const existing = applied.get(migration.sequence);
if (existing !== undefined) {
if (existing.up_hash !== upHash || existing.down_hash !== downHash) {
throw new Error(
`Migration "${migration.name}" (sequence ${migration.sequence}) does not match the migration already applied to the database. It may have been modified after being applied.`
);
}
continue;
}
this._transaction(() => {
db.exec(migration.up);
insertStmt.run(
migration.sequence,
migration.name,
migration.up,
migration.down,
upHash,
downHash
);
});
}View on GitHub (pinned to 9bdd81c7bd)
Solutions
- Do NOT edit the offending migration. Add a NEW migration (incremented sequence) that performs the corrective DDL.
- If you intentionally must rewrite history (dev-only): roll back the migration (run its down via _rollbackObsolete or drop the _migrations row for that sequence) and re-apply — only safe on a DB you can fully recreate.
- If a merge introduced the drift, restore the original SQL of the applied migration and add a fresh migration for the new intent.
- If the DB itself is stale/corrupt from a different branch, delete the SQLite file and let migrations run clean from zero.
Example fix
// before — editing an applied migration's `up`
export const myMigration = { sequence: 3, name: 'add_users', up: 'CREATE TABLE users (...); ALTER TABLE users ADD col x;', down: '...' };
// after — leave sequence 3 untouched, add a new migration
export const addUsersCol = { sequence: 4, name: 'add_users_col_x', up: 'ALTER TABLE users ADD COLUMN x;', down: 'ALTER TABLE users DROP COLUMN x;' }; Defensive patterns
Strategy: validation
Validate before calling
import { createHash } from 'node:crypto';
// Before opening the DB, fail fast if any migration drifted vs. what's applied.
function assertMigrationsConsistent(appliedRows, migrations) {
const bySeq = new Map(appliedRows.map(r => [r.sequence, r]));
for (const m of migrations) {
const a = bySeq.get(m.sequence);
if (!a) continue;
const up = createHash('sha256').update(m.up).digest('hex');
const down = createHash('sha256').update(m.down).digest('hex');
if (a.up_hash !== up || a.down_hash !== down) {
throw new Error(`Migration '${m.name}' seq=${m.sequence} drifted. Restore original SQL or add a new migration.`);
}
}
} Try / catch
try {
return createDatabase(dbPath, options);
} catch (e) {
if (/does not match the migration already applied/.test(e?.message)) {
// hard-stop with actionable guidance — do not auto-rewrite history
throw new Error('DB migration drift detected. Add a NEW migration; do not edit applied ones. Detail: ' + e.message);
}
throw e;
} Prevention
- Treat migrations as immutable once shipped; corrections always go in a new sequence.
- Code-review migration diffs for edits to existing sequence numbers.
- Run a checksum/drift check in CI against the migrations bundle.
- Keep a dev DB reset script so dev DBs are recreatable when history legitimately changes.
When it happens
Trigger: Developer edits an already-shipped migration's up or down SQL; a merge conflict resolution changes SQL text in an old migration; the migrations array is reordered such that the same sequence now maps to different content; running against a database from an older app version whose current build's migration content differs.
Common situations: Editing an existing migration instead of adding a new one to fix a bug; cherry-picking commits that touched migration SQL; copy-paste error producing whitespace-only diffs that still change the hash; CI running against a dev DB that was migrated by a different branch.
Related errors
- Failed to open the database.
- Unknown statement: "${name}"
- The Collection file is corrupted
- Invalid item: missing type
- Unsupported item type: ${itemType}
AI-assisted analysis of usebruno/bruno@9bdd81c7bd (2026-08-13).
Data as JSON: /api/errors/8788b0411776c96c.
Report an issue: GitHub.