immich-app/immich · critical · Error

Failed to read helmet file: ${helmetFile}

Error message

Failed to read helmet file: ${helmetFile}

What it means

getHelmetOptions() reads the helmet configuration file path (from env, defaulting to the packaged helmet.json when the value is 'true'). It attempts readFileSync then JSON.parse; any I/O or parse failure is rethrown as Error('Failed to read helmet file: <path>', { cause }). This runs during server config bootstrap, so a bad helmet file prevents startup.

Source

Thrown at server/src/repositories/config.repository.ts:169

  return new Set(values.length === 0 ? defaults : (values as T[]));
};

const resolveHelmetFile = (helmetFile: 'true' | 'false' | string | undefined) => {
  // default is off
  if (!helmetFile || helmetFile === 'false') {
    return;
  }

  helmetFile =
    helmetFile === 'true'
      ? // eslint-disable-next-line unicorn/prefer-module
        join(__dirname, '..', '..', 'helmet.json')
      : helmetFile;

  try {
    return JSON.parse(readFileSync(helmetFile).toString()) as HelmetOptions;
  } catch (error) {
    throw new Error(`Failed to read helmet file: ${helmetFile}`, { cause: error });
  }
};

const getEnv = (): EnvData => {
  const parseResult = EnvSchema.safeParse(process.env);
  if (!parseResult.success) {
    const messages = ['Invalid environment variables: '];
    for (const issue of parseResult.error.issues) {
      const path = issue.path.join('.');
      messages.push(`  - [${path}] ${issue.message}`);
    }
    throw new Error(messages.join('\n'));
  }
  const dto = parseResult.data;

  const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.Api, ImmichWorker.Microservices]);
  const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []);
  const workers = [...setDifference(includedWorkers, excludedWorkers)];

View on GitHub (pinned to 199723261c)

Solutions

  1. Check the cause on the thrown error to distinguish ENOENT (missing) from SyntaxError (bad JSON) from EACCES (permissions).
  2. Correct the path / fix the JSON / fix file permissions, then restart.
  3. If you do not need a custom helmet config, unset the env var so the default packaged file is used.
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync, readFileSync } from 'node:fs';
if (helmetFile && helmetFile !== 'true' && !existsSync(helmetFile)) {
  throw new Error(`Helmet file not found: ${helmetFile}`);
}
if (helmetFile) JSON.parse(readFileSync(helmetFile, 'utf8')); // pre-validate JSON

Try / catch

try {
  startServer();
} catch (e) {
  if ((e as Error).message.startsWith('Failed to read helmet file')) {
    console.error((e as Error).cause); // ENOENT vs SyntaxError
    // fix path/JSON/perms then restart
  } else throw e;
}

Prevention

When it happens

Trigger: Setting the helmet env var to a path that does not exist, is unreadable, or contains invalid JSON; or the default packaged helmet.json is missing/corrupt in the deployment.

Common situations: Custom deployment that mounts a helmet config with a typo'd path; permission error reading the file; hand-edited JSON with a trailing comma; broken container image missing the bundled file.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/ec514a21c1b0af2a. Report an issue: GitHub.