actualbudget/actual · error

File name matches multiple files: ${path}

Error message

File name matches multiple files: ${path}

What it means

During YNAB4 import, getFile filters zip entries for an exact path match and throws when two or more entries have the identical name. YNAB4 exports normally contain unique entry names, so this signals a malformed or duplicated archive.

Source

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

  unixFilepath = unixFilepath.replace(/\.zip$/, '').replace(/.ynab4$/, '');

  // Most budgets are named like "Budget~51938D82.ynab4" but sometimes
  // they are only "Budget.ynab4". We only want to grab the name
  // before the ~ if it exists.
  const m = unixFilepath.match(/([^/~]+)[^/]*$/);
  if (!m) {
    return null;
  }
  return m[1];
}

function getFile(entries: string[], path: string) {
  const files = entries.filter(e => e === path);
  if (files.length === 0) {
    throw new Error('Could not find file: ' + path);
  }
  if (files.length >= 2) {
    throw new Error('File name matches multiple files: ' + path);
  }
  return files[0];
}

function join(...paths: string[]): string {
  return paths.slice(1).reduce(
    (full, path) => {
      return full + '/' + path.replace(/^\//, '');
    },
    paths[0].replace(/\/$/, ''),
  );
}

export function parseFile(buffer: Buffer): YNAB4.YFull {
  let zipped: Record<string, Uint8Array>;
  try {
    zipped = safeUnzip(buffer);
  } catch (e) {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Re-export the budget from YNAB4 to get a clean archive with unique entry names.
  2. Rebuild the zip from an extracted copy so each file appears once.
  3. Inspect with unzip -l to find the duplicated entries and remove one when re-zipping.
Defensive patterns

Strategy: validation

Validate before calling

function hasDuplicateEntries(buffer) {
  const entries = Object.keys(safeUnzip(buffer));
  return new Set(entries).size !== entries.length;
}

Type guard

function isWellFormedZip(buffer) {
  try {
    const entries = Object.keys(safeUnzip(buffer));
    return entries.length > 0 && new Set(entries).size === entries.length;
  } catch { return false; }
}

Try / catch

try {
  const budget = parseFile(buffer);
} catch (e) {
  if (e.message.startsWith('File name matches multiple files')) {
    // rebuild the archive from an extracted copy
  } else throw e;
}

Prevention

When it happens

Trigger: parseFile(buffer) called on a zip containing two entries with the exact same path string (e.g. duplicate Budget.yfull entries produced by concatenating archives or a buggy zip writer).

Common situations: Merged or re-compressed budget archives; zip files repaired by tools that duplicated entries; archives edited programmatically and re-written with the same entry added twice.

Related errors


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