actualbudget/actual · critical · Error
out-of-sync-migrations
out-of-sync-migrations
Error message
out-of-sync-migrations
What it means
checkDatabaseValidity compares the migrations applied to a database against the available migration list. If the applied index exceeds the number of available migrations, the database was made by a newer version of the app and the code logs the applied/available ids then throws this coded error to abort migrate() and prevent running on an incompatible schema.
Source
Thrown at packages/loot-core/src/server/migrate/migrations.ts:158
}
sqlite.runQuery(db, 'INSERT INTO __migrations__ (id) VALUES (?)', [
getMigrationId(name),
]);
}
function checkDatabaseValidity(
appliedIds: number[],
available: string[],
): void {
if (appliedIds.length > available.length) {
logger.error(
'Database is out of sync with migrations (index past available):',
{
appliedIds,
available,
},
);
throw new Error('out-of-sync-migrations');
}
for (let i = 0; i < appliedIds.length; i++) {
if (appliedIds[i] !== getMigrationId(available[i])) {
logger.error(
'Database is out of sync with migrations (migration id mismatch):',
{
appliedIds,
available,
missing: available.filter(
m => !appliedIds.includes(getMigrationId(m)),
),
},
);
throw new Error('out-of-sync-migrations');
}
}
}View on GitHub (pinned to d4334cb6e6)
Solutions
- Upgrade Actual (app and, if self-hosting, the sync server) to a version at or above the one that last modified the budget.
- Do not attempt to open the budget with the older version; revert the downgrade instead.
- If you must stay on the old version, restore a backup of the budget taken while that old version was in use.
Defensive patterns
Strategy: validation
Validate before calling
// compare applied vs available before opening
const applied = await getAppliedMigrations(db);
const available = migrations.length;
const compatible = applied.length <= available;
if (!compatible) throw new Error('budget made by newer version — upgrade Actual'); Type guard
function isCompatible(appliedIds, availableMigrations) {
return appliedIds.length <= availableMigrations.length;
} Try / catch
try {
await migrate(db);
} catch (e) {
if (e.message === 'out-of-sync-migrations') {
// upgrade the app/server; do not touch the budget file
} else throw e;
} Prevention
- Never downgrade Actual below the version that last opened a budget.
- When self-hosting, upgrade the sync server before or together with clients.
- Keep database backups before version changes.
- Test risky upgrades on a copy of the budget first.
When it happens
Trigger: Opening a budget file that was migrated by a newer version of Actual (more migrations applied) with an older installed version — e.g. after downgrading the app or syncing a budget from a newer client.
Common situations: Downgrading the desktop app or self-hosted server below the version that last touched the budget; switching between stable and bleeding-edge releases; opening a budget copied from a newer install.
Related errors
- Error importing budget: no budget was loaded
- Error exporting budget: no data was returned
- Query result missing data
- Transfer payee with account ID ${transferAccountId} not foun
- Bank with ID ${bankId} not found.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/6890286f300d5e87.
Report an issue: GitHub.