actualbudget/actual · error · FileDownloadError

invalid-meta-file

invalid-meta-file

Error message

invalid-meta-file

What it means

FileDownloadError('invalid-meta-file') is thrown by importBuffer when metadata.json inside the downloaded archive cannot be parsed as JSON. The db.sqlite file was found, but the accompanying metadata (budget id, name, cloud ids) is corrupt, so the budget cannot be registered locally.

Source

Thrown at packages/loot-core/src/server/cloud-storage.ts:253

    : sharedDirs.length === 1
      ? sharedDirs[0]
      : null;

  if (dir == null) {
    throw FileDownloadError('invalid-zip-file');
  }

  const entryName = dir + 'db.sqlite';
  const metaEntryName = dir + 'metadata.json';

  const dbContent = Buffer.from(entries[entryName]);
  const metaContent = Buffer.from(entries[metaEntryName]);

  let meta;
  try {
    meta = JSON.parse(metaContent.toString('utf8'));
  } catch {
    throw FileDownloadError('invalid-meta-file');
  }

  // Update the metadata. The stored file on the server might be
  // out-of-date with a few keys
  meta = {
    ...meta,
    cloudFileId: fileData.fileId,
    groupId: fileData.groupId,
    lastUploaded: monthUtils.currentDay(),
    encryptKeyId: fileData.encryptMeta ? fileData.encryptMeta.keyId : null,
  };

  const budgetDir = fs.getBudgetDir(meta.id);

  if (await fs.exists(budgetDir)) {
    // Don't remove the directory so that backups are retained
    const dbFile = fs.join(budgetDir, 'db.sqlite');
    const metaFile = fs.join(budgetDir, 'metadata.json');

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Re-download the file from the server; the local copy may be corrupted in transit
  2. Restore the budget from a local backup (files/backups directory) and re-upload it
  3. If the metadata is recoverable, fix metadata.json to be valid JSON with at least an "id" field, then re-zip
  4. Check sync-server logs/storage for corruption and re-upload a known-good export

Example fix

// before: trusting archive metadata
await importActual(buffer);
// after: pre-validate metadata JSON
const meta = JSON.parse(zip.entries['metadata.json'].toString('utf8')); // throws early with clearer error
if (!meta.id) throw new Error('metadata.json missing id');
Defensive patterns

Strategy: validation

Validate before calling

function validateArchiveMeta(entries) {
  const entry = Object.keys(entries).find(n => n === 'metadata.json' || n.endsWith('/metadata.json'));
  if (!entry) throw new Error('metadata.json missing');
  const meta = JSON.parse(Buffer.from(entries[entry]).toString('utf8'));
  if (typeof meta.id !== 'string' || !meta.id) throw new Error('metadata.json has no id');
  return meta;
}

Type guard

function isValidBudgetMeta(v) {
  return typeof v === 'object' && v !== null && typeof v.id === 'string' && v.id.length > 0;
}

Try / catch

try {
  await importActual(buffer);
} catch (e) {
  if (e instanceof FileDownloadError && e.reason === 'invalid-meta-file') {
    console.error('metadata.json is corrupt; restore from backup or re-download.');
  } else throw e;
}

Prevention

When it happens

Trigger: calling download() or importActual() with an archive whose metadata.json entry is empty, truncated, or contains invalid JSON.

Common situations: a partially uploaded/corrupted cloud file, archives edited by hand where metadata.json was mangled, disk corruption on the sync server, or encoding damage from binary-mode text edits.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/0a68b1d37d7298e2. Report an issue: GitHub.