actualbudget/actual · error · Error

Error reading Budget.yfull file

Error message

Error reading Budget.yfull file

What it means

After locating Budget.yfull inside the zip, parseFile converts the entry bytes to a UTF-8 string inside a try/catch and throws this error if reading fails — typically because getFile (290/291) or the buffer conversion failed, so the underlying cause is usually a missing/misplaced entry rather than an I/O problem.

Source

Thrown at packages/loot-core/src/server/importers/ynab4.ts:470

  const metaStr = Buffer.from(zipped[getFile(entries, root + 'Budget.ymeta')]);
  const meta = JSON.parse(metaStr.toString('utf8'));
  const budgetPath = join(root, meta.relativeDataFolderName);

  const deviceFiles = entries.filter(e =>
    e.startsWith(join(budgetPath, 'devices')),
  );
  const deviceGUID = findLatestDevice(zipped, deviceFiles);

  const yfullPath = join(budgetPath, deviceGUID, 'Budget.yfull');
  let contents;
  try {
    contents = Buffer.from(zipped[getFile(entries, yfullPath)]).toString(
      'utf8',
    );
  } catch (e) {
    logger.log(e);
    throw new Error('Error reading Budget.yfull file');
  }

  try {
    return JSON.parse(contents);
  } catch {
    throw new Error('Error parsing Budget.yfull file');
  }
}

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Check the import logs for the wrapped original error (e.g. 'Could not find file: ...') — fix the zip layout so Budget.yfull is at the expected path.
  2. Re-export the budget from YNAB4 with the default folder structure and retry.
  3. Confirm the archive actually contains Budget.yfull at its root folder (unzip -l).
  4. If the file is present but binary-corrupted, restore from another backup.
Defensive patterns

Strategy: validation

Validate before calling

function zipContainsYfull(buffer) {
  const entries = Object.keys(safeUnzip(buffer));
  return entries.some(e => e.endsWith('Budget.yfull')) &&
    entries.find(e => e.endsWith('Budget.yfull')).length > 0;
}

Type guard

function isYnab4Layout(entries) {
  return Array.isArray(entries) &&
    entries.some(e => /[^/]*\.ynab4\//.test(e)) &&
    entries.some(e => e.endsWith('Budget.yfull'));
}

Try / catch

try {
  const budget = parseFile(buffer);
} catch (e) {
  if (e.message === 'Error reading Budget.yfull file') {
    // check logs for the wrapped cause (missing entry); fix zip layout
  } else throw e;
}

Prevention

When it happens

Trigger: parseFile(buffer) where the Budget.yfull entry lookup throws (wrong path/case, duplicate entries) or the zipped entry is absent/undefined so Buffer.from fails to produce readable UTF-8 content.

Common situations: Re-zipped archives with renamed inner directories; partial uploads missing Budget.yfull; importing an archive whose root folder pattern /([^/]*\.ynab4)/ does not match so the computed path is wrong.

Related errors


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