can1357/oh-my-pi · critical · Error

Backup failed integrity check: ${backupPath}

Error message

Backup failed integrity check: ${backupPath}

What it means

restoreBackup() verifies every candidate database with an SQLite integrity check before replacing the live database. If the gunzipped backup file at tempPath fails verifyIntegrity, it throws this error naming the original backup path. The live target database is untouched in this case because the snapshot/rename happens after verification.

Source

Thrown at packages/mnemopi/src/dr/recovery.ts:259

	if (isSqliteFile(uncompressed)) {
		writeFileSync(tempPath, uncompressed, { flag: "wx" });
		return;
	}
	writeGzippedSqlDump(uncompressed.toString("utf8"), tempPath);
}

export function restoreBackup(backupPath: string, dbPath?: string | null): RestoreResult {
	const targetPath = dbPath ?? getDefaultPaths().dbPath;
	if (!existsSync(backupPath)) throw new FileNotFoundError(`Backup not found: ${backupPath}`);

	mkdirSync(dirname(targetPath), { recursive: true });

	const uncompressed = gunzipSync(readFileSync(backupPath));
	const tempPath = restoreTempPath(targetPath);
	let replacedTarget = false;
	try {
		writeRestoreCandidate(uncompressed, tempPath);
		if (!verifyIntegrity(tempPath)) throw new Error(`Backup failed integrity check: ${backupPath}`);
		snapshotCurrentDatabase(targetPath);
		renameSync(tempPath, targetPath);
		replacedTarget = true;
		removeSqliteSidecars(targetPath);
		const integrity = verifyIntegrity(targetPath);
		if (!integrity) throw new Error(`Restored database failed integrity check: ${backupPath}`);
		return {
			restored: true,
			backup_used: backupPath,
			database_path: targetPath,
			integrity_check: integrity,
		};
	} catch (error) {
		try {
			rmSync(tempPath, { force: true });
		} catch {
			// Preserve the restore failure.
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the backup file independently: run `sqlite3 <gunzipped-file> 'PRAGMA integrity_check;'` to see the specific corruption.
  2. Regenerate a fresh backup with backupDatabase() and retry the restore.
  3. Check disk space and the gzip file (`gzip -t`) for truncation/corruption.
  4. Restore an older backup file from the same directory that passes integrity checks.

Example fix

// before
const result = restoreBackup('/backups/mnemopi_backup_latest.db.gz', dbPath);
// after
import { verifyIntegrity } from './recovery';
const uncompressed = gunzipSync(readFileSync('/backups/mnemopi_backup_latest.db.gz'));
writeFileSync('/tmp/candidate.db', uncompressed);
if (!verifyIntegrity('/tmp/candidate.db')) {
  console.error('Backup corrupt, picking older backup');
}
const result = restoreBackup('/backups/mnemopi_backup_latest.db.gz', dbPath);
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync, readFileSync } from 'node:fs';
import { gunzipSync } from 'node:zlib';
function backupLooksValid(path) {
  if (!existsSync(path)) return false;
  try {
    const header = gunzipSync(readFileSync(path)).subarray(0, 16);
    return header.toString('utf8').startsWith('SQLite format 3');
  } catch { return false; }
}
if (!backupLooksValid(backupPath)) throw new Error(`Backup unreadable: ${backupPath}`);
const result = restoreBackup(backupPath, targetPath);

Type guard

function isNonEmptyString(v) { return typeof v === 'string' && v.length > 0; }
function canRestore(backupPath) {
  return isNonEmptyString(backupPath) && backupPath.endsWith('.db.gz');
}

Try / catch

try {
  const result = restoreBackup(backupPath, targetPath);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Backup failed integrity check')) {
    // pick next-oldest backup; live DB untouched
  } else throw err;
}

Prevention

When it happens

Trigger: Calling restoreBackup(backupPath, targetPath) where the decompressed backup is corrupt, truncated, or not a valid SQLite database file (verifyIntegrity returns false).

Common situations: Backups produced by a crashed backup job, gzip files corrupted by partial writes or disk-full, copying .db files while SQLite had active WAL sidecars, restoring across incompatible SQLite versions, or manual edits to a backup file.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/fe1931e4e72a6af0. Report an issue: GitHub.