immich-app/immich · error

Unable to complete move. File checksum mismatch

Error message

Unable to complete move. File checksum mismatch: ${newChecksum.toString('base64')} !== ${checksum.toString('base64')}

What it means

When storage-template hash verification is enabled and the move is for an asset, the copied file's SHA-1 checksum is compared to the checksum stored for the asset. Any difference fails verification, and this warning is logged with both base64 checksums before the caller rolls back the copy.

Solutions

  1. Verify the stored checksum against the ORIGINAL file; if the original also mismatches, re-generate checksums
  2. Check disk health (SMART) on both source and destination
  3. Re-copy the file and re-run verification
  4. Temporarily disable hashVerificationEnabled to isolate whether size or hash check fails, then re-enable
Defensive patterns

Strategy: retry

Validate before calling

const expected = assetInfo.checksum.toString('base64');
const actual = (await hashFile(newPath)).toString('base64');
if (expected !== actual) await fs.promises.unlink(newPath); // roll back before proceeding

Try / catch

if (!(await verifyNewPathContentsMatchesExpected(oldPath, newPath, assetInfo))) {
  await storage.unlink(newPath);
  logger.warn(`hash mismatch expected=${expected}`);
  return moveFailed();
}

Prevention

When it happens

Trigger: moveFile -> verifyNewPathContentsMatchesExpected with assetInfo present and config.storageTemplate.hashVerificationEnabled=true; cryptoRepository.hashFile(newPath) differs from assetInfo.checksum — copy corrupted data or the stored checksum is stale/wrong.

Common situations: Bit rot or corruption during copy on failing disks; asset checksum recorded before a later metadata-side change; mismatched checksum algorithm after version migration; file mutated by another process between copy and hash.

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

Appendix: source

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

    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;
      }
      this.logger.debug(`File checksum check: ${newChecksum.toString('base64')} === ${checksum.toString('base64')}`);
    }
    return true;
  }

  ensureFolders(input: string) {
    this.storageRepository.mkdirSync(dirname(input));
  }

  removeEmptyDirs(folder: StorageFolder) {
    return this.storageRepository.removeEmptyDirs(StorageCore.getBaseFolder(folder));
  }

View on GitHub (pinned to e55ac299a4)