immich-app/immich · warning

Unable to complete move. File size mismatch

Error message

Unable to complete move. File size mismatch: ${newPathSize} !== ${oldPathSize}

What it means

verifyNewPathContentsMatchesExpected compares the new file's size against the old file's size (or the asset's recorded sizeInBytes). On mismatch it logs this warning and returns false, causing callers to treat the move as failed and roll back the copied file.

Solutions

  1. Re-run the move once source and destination are stable
  2. Check destination free space
  3. Freeze writes to the asset (stop sync/upload jobs) during migration
  4. Compare checksums manually to see if only size metadata differs
Defensive patterns

Strategy: retry

Validate before calling

const [oldStat, newStat] = await Promise.all([fs.stat(oldPath), fs.stat(newPath)]);
if (oldStat.size !== newStat.size) throw new Error('size mismatch, retry copy');

Try / catch

if (!(await verifyNewPathContentsMatchesExpected(oldPath, newPath, assetInfo))) {
  await unlink(newPath);
  await retryCopy(oldPath, newPath, { attempts: 3, backoff: 'exponential' });
}

Prevention

When it happens

Trigger: moveFile's copy fallback calls this verifier; stat(newPath).size differs from oldStat.size or assetInfo.sizeInBytes — copy did not fully complete or the source changed during copy.

Common situations: Disk filled up mid-copy; source asset being uploaded/synced concurrently; filesystem reporting sparse/incorrect sizes; copy interrupted by unmount.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

      }
    }

    await this.savePath(pathType, entityId, newPath, ownerId);
    await this.moveRepository.delete(move.id);
  }

  private async verifyNewPathContentsMatchesExpected(
    oldPath: string,
    newPath: string,
    assetInfo?: { sizeInBytes: number; checksum: Buffer },
  ) {
    const oldStat = await this.storageRepository.stat(oldPath);
    const newStat = await this.storageRepository.stat(newPath);
    const oldPathSize = assetInfo ? assetInfo.sizeInBytes : oldStat.size;
    const newPathSize = newStat.size;
    this.logger.debug(`File size check: ${newPathSize} === ${oldPathSize}`);
    if (newPathSize !== oldPathSize) {
      this.logger.warn(`Unable to complete move. File size mismatch: ${newPathSize} !== ${oldPathSize}`);
      return false;
    }
    const repos = {
      configRepo: this.configRepository,
      metadataRepo: this.systemMetadataRepository,
      logger: this.logger,
    };
    const config = await getConfig(repos, { withCache: true });
    if (assetInfo && config.storageTemplate.hashVerificationEnabled) {
      const { checksum } = assetInfo;
      const newChecksum = await this.cryptoRepository.hashFile(newPath);
      if (!newChecksum.equals(checksum)) {
        this.logger.warn(
          `Unable to complete move. File checksum mismatch: ${newChecksum.toString('base64')} !== ${checksum.toString(
            'base64',
          )}`,
        );
        return false;

View on GitHub (pinned to e55ac299a4)