immich-app/immich · warning

Unable to resolve realpath

Error message

Unable to resolve realpath

What it means

This warning is logged by the download service when resolving the symbolic/real path of a file to include in a downloadable archive fails. The storage repository's realpath call throws (e.g. the file vanished or the link is broken), and instead of failing the whole archive, the service logs and falls back to the un-resolved path. It is non-fatal by design.

Solutions

  1. Verify the file exists at originalPath/editedPath on the storage volume and the symlink target is intact
  2. Check that external library volumes are mounted and reachable by the server
  3. Re-run the download; if persistent, refresh the library so dead entries are purged
  4. If path rewriting matters, fix permissions on parent directories so realpath can traverse them

Example fix

// before
try { realpath = await this.storageRepository.realpath(realpath); } catch { this.logger.warn('Unable to resolve realpath', { originalPath }); }
// after
try { realpath = await this.storageRepository.realpath(realpath); } catch (err) { this.logger.warn('Unable to resolve realpath', { originalPath, error: err }); if (!(await this.storageRepository.exists(realpath))) { continue; } }
Defensive patterns

Strategy: try-catch

Validate before calling

import { stat, realpath } from 'fs/promises';
async function pathResolvable(p: string): Promise<boolean> {
  try { await realpath(p); return true; } catch { return false; }
}

Try / catch

try {
  realpath = await storageRepository.realpath(realpath);
} catch {
  logger.warn('Unable to resolve realpath', { originalPath });
  // decide: skip the file or proceed with un-resolved path
}

Prevention

When it happens

Trigger: A user downloads an archive while one of the selected files was deleted/moved on disk between selection and zip creation, or the path is a broken symlink that realpath cannot resolve on the storage backend.

Common situations: Race between asset selection and download; external storage/volume remounted or offline; broken symlinks in the library; file removed by a concurrent library-deletion job.

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/19b531517aecd1cf. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/download.service.ts:117

        continue;
      }

      const { originalPath, editedPath, originalFileName } = asset;

      let filename = sanitize(originalFileName) || 'unnamed';
      const count = paths[filename] || 0;
      paths[filename] = count + 1;
      if (count !== 0) {
        const parsedFilename = parse(filename);
        filename = `${parsedFilename.name}+${count}${parsedFilename.ext}`;
      }

      let realpath = dto.edited && editedPath ? editedPath : originalPath;

      try {
        realpath = await this.storageRepository.realpath(realpath);
      } catch {
        this.logger.warn('Unable to resolve realpath', { originalPath });
      }

      zip.addFile(realpath, filename);
    }

    void zip.finalize();

    return {
      stream: zip.stream,
      disposition: dto.archiveName && `attachment; filename*=UTF-8''${encodeURIComponent(dto.archiveName)}.zip`,
    };
  }
}

View on GitHub (pinned to e55ac299a4)