immich-app/immich · critical

Migration " " was already applied to this database but is…

Error message

Migration "${missing[1]}" was already applied to this database but is not in this version of Immich (${serverVersion}). This usually means the database was migrated by a newer version. Downgrades are not supported.

What it means

After a failed migration run, Immich inspects the error for the Kysely 'previously executed migration ... is missing' signature. That means the migrations table records migrations that don't exist in this Immich version's migration set — i.e. the database was migrated by a NEWER Immich build, and downgrading is unsupported. Immich rethrows with a human-friendly explanation including the offending migration name and server version.

Solutions

  1. Restore the newer Immich version that created those migrations (roll the image tag back forward)
  2. Restore a database backup taken before the newer version ran its migrations, then run the older version
  3. Never downgrade Immich across DB migrations; check the migrations table (select * from migrations order by name desc;) to confirm the offending entry
  4. If a single bad record is blocking intentionally and you accept data risk, only as a last resort align the migrations table with the old version's migration set — unsupported and likely to corrupt schema state

Example fix

// before (docker-compose.yml)
image: ghcr.io/immich-app/immich-server:v1.118.0
// after — return to the version that migrated the DB
image: ghcr.io/immich-app/immich-server:v1.122.0
Defensive patterns

Strategy: try-catch

Validate before calling

const applied = await sql`SELECT name FROM migrations`;
const known = new Set(allMigrations.map((m) => m.name));
const unknown = applied.filter((r) => !known.has(r.name));
if (unknown.length > 0) throw new Error(`DB has migrations from a newer version: ${unknown.map((u) => u.name).join(', ')}`);

Try / catch

try {
  await repo.runMigrations(force);
} catch (e) {
  if ((e as Error).message.includes('Downgrades are not supported')) {
    logger.fatal('Deploy the newer Immich version or restore a pre-migration DB backup; aborting startup.');
    process.exit(1);
  }
  throw e;
}

Prevention

When it happens

Trigger: Starting an older Immich version against a database whose migrations table contains migrations from a newer Immich build (e.g. rolling back a container tag from v1.x to v1.(x-1)).

Common situations: Pinning/rolling back the Immich Docker image after a newer version already ran migrations; restoring an old app container against a newer database volume; alternate deployments (e.g. a test instance) sharing the database.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/6a99043e80d33ef2. Report an issue: GitHub.

Appendix: source

Thrown at server/src/repositories/database.repository.ts:402

    const migrator = this.createMigrator();

    const { error, results = [] } = await migrator.migrateToLatest();

    for (const result of results) {
      if (result.status === 'Success') {
        this.logger.log(`Migration "${result.migrationName}" succeeded`);
      } else if (result.status === 'Error') {
        this.logger.warn(`Migration "${result.migrationName}" failed`);
      }
    }

    if (error) {
      this.logger.error(`Migrations failed: ${error}`);

      const missing =
        error instanceof Error ? error.message.match(/previously executed migration (.+) is missing/u) : null;
      if (missing) {
        throw new Error(
          `Migration "${missing[1]}" was already applied to this database but is not in this version of Immich (${serverVersion}). ` +
            `This usually means the database was migrated by a newer version. Downgrades are not supported.`,
          { cause: error },
        );
      }

      throw error;
    }

    this.logger.log('Finished running migrations');
    return results.length;
  }

  async migrateFilePaths(sourceFolder: string, targetFolder: string): Promise<void> {
    // remove trailing slashes
    if (sourceFolder.endsWith('/')) {
      sourceFolder = sourceFolder.slice(0, -1);
    }

View on GitHub (pinned to e55ac299a4)