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

  1. Upgrade Actual (app and, if self-hosting, the sync server) to a version at or above the one that last modified the budget.
  2. Do not attempt to open the budget with the older version; revert the downgrade instead.
  3. 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

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


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/6890286f300d5e87. Report an issue: GitHub.