immich-app/immich · error

Geodata file not found

Error message

Geodata file ${filePath} not found

What it means

loadAdmin checks that an admin1/admin2 geodata file exists before streaming it into a Map. It logs and throws this error when existsSync(filePath) is false. Like loadCities500, it signals a missing geodata asset or a misconfigured file path in the resourcePaths config.

Solutions

  1. Place the admin1/admin2 geodata files at the configured filePath
  2. Fix the resourcePaths.geodata admin file paths in the env config
  3. Ensure the geodata directory is included in the deployment (image build step or volume mount)
  4. Verify file permissions allow the server process to stat/read the file

Example fix

// before
// geodata.admin1 = ./data/admin1Codes.txt (missing)
// after
// curl -o ./data/admin1Codes.txt https://download.geonames.org/export/dump/admin1CodesASCII.txt
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync } from 'fs';
for (const f of [admin1Path, admin2Path]) {
  if (!existsSync(f)) throw new Error(`Admin geodata missing: ${f}`);
}

Type guard

const fileExists = (p: unknown): p is string => typeof p === 'string' && existsSync(p);

Try / catch

try {
  await mapRepo.importGeodata();
} catch (e) {
  if (String(e.message).startsWith('Geodata file')) {
    logger.warn(`Admin geodata unavailable: ${e.message}`);
    return; // degrade gracefully
  }
  throw e;
}

Prevention

When it happens

Trigger: importGeodata() invokes loadAdmin() (via admin1/admin2 callers) with a filePath that does not exist on disk, so existsSync(filePath) fails and the error is thrown.

Common situations: admin1Codes/admin2 geodata files not downloaded during setup; path typo in env config; missing volume mount in container; renamed file after a GeoNames data update.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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

Appendix: source

Thrown at server/src/repositories/map.repository.ts:349

      await this.db.insertInto('geodata_places').values(bufferGeodata).execute();
      count += bufferGeodata.length;
    }

    await Promise.all(futures);

    const duration = performance.now() - startTime;
    const seconds = duration / 1000;
    const recordsPerSecond = Math.round(count / seconds);

    this.logger.log(
      `Successfully imported ${count} geodata records in ${seconds.toFixed(2)}s (${recordsPerSecond} records/second)`,
    );
  }

  private async loadAdmin(filePath: string) {
    if (!existsSync(filePath)) {
      this.logger.error(`Geodata file ${filePath} not found`);
      throw new Error(`Geodata file ${filePath} not found`);
    }

    const input = createReadStream(filePath, { highWaterMark: 512 * 1024 * 1024 });
    const lineReader = readLine.createInterface({ input });

    const adminMap = new Map<string, string>();
    for await (const line of lineReader) {
      const lineSplit = line.split('\t');
      adminMap.set(lineSplit[0], lineSplit[1]);
    }

    return adminMap;
  }

  private createGeodataIndices() {
    return Promise.all([
      sql`ALTER TABLE geodata_places ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`.execute(this.db),
      this.db.schema

View on GitHub (pinned to e55ac299a4)