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
- Re-run the move once source and destination are stable
- Check destination free space
- Freeze writes to the asset (stop sync/upload jobs) during migration
- 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
- Check free space before copy
- Do not mutate source files while a move is in flight
- Log old/new sizes on mismatch for diagnosis
- Retry copies once rather than leaving files half-migrated
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
- Skipping move due to file size mismatch
- Unable to complete move. File checksum mismatch
- Attempted to clear cache, but rmtree is not safe on this…
- Failed to create
- Failed to read: " ( )
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)