{"record":{"id":"eb8f30ed2d88309b","repo":"actualbudget/actual","slug":"error-reading-backup-zip-file","errorCode":null,"errorMessage":"Error reading backup zip file","messagePattern":"Error reading backup zip file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/budgetfiles/backups.ts","lineNumber":243,"sourceCode":"\n    // Re-upload the new file\n    try {\n      await cloudStorage.upload();\n    } catch {}\n\n    prefs.unloadPrefs();\n\n    const zipContent = await fs.readFile(\n      fs.join(budgetDir, 'backups', backupId),\n      'binary',\n    );\n\n    let entries: Record<string, Uint8Array>;\n    try {\n      entries = safeUnzip(zipContent);\n    } catch (e) {\n      logger.log(e);\n      throw new Error('Error reading backup zip file');\n    }\n    if (!entries['db.sqlite'] || !entries['metadata.json']) {\n      throw new Error('Backup zip file is missing db.sqlite or metadata.json');\n    }\n\n    await fs.writeFile(fs.join(budgetDir, 'db.sqlite'), entries['db.sqlite']);\n    await fs.writeFile(\n      fs.join(budgetDir, 'metadata.json'),\n      entries['metadata.json'],\n    );\n  }\n}\n\nexport function startBackupService(id: string) {\n  if (serviceInterval) {\n    clearInterval(serviceInterval);\n  }\n","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/budgetfiles/backups.ts#L225-L261","documentation":"loadBackup extracts a backup zip via safeUnzip (which enforces zip-slip and entry-size safety). If the archive cannot be parsed at all — corrupt data, unsupported compression, or a malicious structure rejected by safeUnzip — the underlying error is logged and rethrown as 'Error reading backup zip file'.","triggerScenarios":"Calling loadBackup with zipContent that is not a valid zip: truncated download, HTML error page saved as .zip, empty buffer, or an archive using an encoding safeUnzip rejects (e.g. encrypted or zip64).","commonSituations":"Restoring a backup copied from a failed sync; a cloud restore downloaded partially; user picks the wrong file (e.g. a .blob or metadata file) in the restore dialog; manually renamed file with .zip extension.","solutions":["Verify the file is a real, complete zip (open it with unzip --test or check the PK header and End-of-Central-Directory record).","Re-download or re-copy the backup and confirm the byte size matches the source.","If restoring from cloud storage, retry the fetch — transient truncation is common.","Check the log output (logger.log(e)) for the underlying safeUnzip reason (encryption, unsupported method) and re-create the backup without those features.","Fall back to another backup file; the current one is unrecoverable if the central directory is corrupt."],"exampleFix":"// before\nawait send('load-backup', { id: backupId }); // file was a partial download\n// after\nconst buf = await fs.readFile(path); // verify first\nif (buf.length < 4 || buf.readUInt32LE(0) !== 0x04034b50) {\n  throw new Error('Not a valid zip: ' + path);\n}\nawait send('load-backup', { id: backupId });","handlingStrategy":"validation","validationCode":"function looksLikeZip(buf: Uint8Array) {\n  return buf.length >= 4 && buf[0] === 0x50 && buf[1] === 0x4b; // 'PK'\n}\nif (!looksLikeZip(zipContent)) throw new Error('Selected file is not a valid zip backup');","typeGuard":"function isZipBackup(data: unknown): data is Uint8Array {\n  return data instanceof Uint8Array && data.length > 4 && data[0] === 0x50 && data[1] === 0x4b;\n}","tryCatchPattern":"try {\n  await send('load-backup', { id });\n} catch (e) {\n  if (e.message === 'Error reading backup zip file') {\n    console.error('Backup archive is corrupt; choose another backup', e);\n  } else throw e;\n}","preventionTips":["Verify backup files open with unzip --test before relying on them.","Check byte sizes after downloads/copies to catch truncation.","Keep multiple backup generations; never restore from a single suspect file.","Don't rename arbitrary files to .zip and attempt restores."],"tags":["backup","zip","corruption","restore"],"backgroundTag":"corrupt-archive","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}