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
- Place the admin1/admin2 geodata files at the configured filePath
- Fix the resourcePaths.geodata admin file paths in the env config
- Ensure the geodata directory is included in the deployment (image build step or volume mount)
- 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
- Bundle admin1/admin2 files in the same download step as cities500
- Log the resolved absolute path at startup so misconfigurations are visible early
- Pin/verify geodata file versions after provider data updates
- Verify file read permissions for the server's runtime user
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
- Geodata file not found
- Device ' ' does not exist. If using Docker, make sure this…
- Failed to add job handler for
- Failed to find job handler for Job.
- Failed to verify SMTP configuration
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.schemaView on GitHub (pinned to e55ac299a4)