immich-app/immich · warning

Unable to save person path without an owner

Error message

Unable to save person path without an owner

What it means

savePath persists the new path after a move. For a person-face thumbnail it requires the face's ownerId; if it is missing, the operation logs this warning and silently returns, so the person record keeps pointing at the old thumbnail path.

Solutions

  1. Backfill the missing ownerId on the person/face record and re-run the move
  2. Investigate how an ownerless person record was created and fix the import/migration path
  3. Clean up orphaned person rows so they are not moved at all
  4. Treat the warning as data-integrity signal and audit person table for null owners

Example fix

// before
await storageCore.moveFile(PersonPathType.Face, personId, newPath); // ownerId missing
// after
await storageCore.moveFile(PersonPathType.Face, personId, newPath, ownerId: person.ownerId);
Defensive patterns

Strategy: type-guard

Validate before calling

const person = await personRepo.getById(id);
if (!person?.ownerId) throw new Error(`Person ${id} has no owner; fix data before moving`);

Type guard

function hasOwner(p: { ownerId?: string | null }): p is { ownerId: string } { return typeof p.ownerId === 'string' && p.ownerId.length > 0; }

Try / catch

try {
  await storageCore.moveFile(PersonPathType.Face, id, newPath, ownerId);
} catch (e) {
  logger.error('person move failed, keeping old path', e);
  await personRepo.update({ ownerId, thumbnailPath: oldPath }); // stay consistent
}

Prevention

When it happens

Trigger: moveFile -> savePath with pathType Face (PersonPathType.Face) and ownerId is null/undefined — moving a person thumbnail for a face row lacking an owner.

Common situations: Orphaned person/face rows from imports or migrations without owner linkage; data repaired manually and ownerId dropped; bugs in face-clustering pipelines creating ownerless records.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at server/src/cores/storage.core.ts:342

  private savePath(pathType: PathType, id: string, newPath: string, ownerId?: string) {
    switch (pathType) {
      case AssetPathType.Original: {
        return this.assetRepository.update({ id, originalPath: newPath });
      }

      case AssetFileType.FullSize:
      case AssetFileType.EncodedVideo:
      case AssetFileType.Thumbnail:
      case AssetFileType.Preview:
      case AssetFileType.Sidecar:
      case AssetPathType.EncodedVideo: {
        return this.assetRepository.upsertFile({ assetId: id, type: pathType as AssetFileType, path: newPath });
      }

      case PersonPathType.Face: {
        if (!ownerId) {
          this.logger.warn('Unable to save person path without an owner');
          return;
        }

        return this.personRepository.update({ ownerId, personGroupId: id, thumbnailPath: newPath });
      }

      case UserPathType.Profile: {
        this.logger.warn('Unexpected path type:', pathType);
        return;
      }
    }
  }

  static getNestedFolder(folder: StorageFolder, ownerId: string, filename: string): string {
    return join(StorageCore.getFolderLocation(folder, ownerId), filename.slice(0, 2), filename.slice(2, 4));
  }

  static getNestedPath(folder: StorageFolder, ownerId: string, filename: string): string {

View on GitHub (pinned to e55ac299a4)