{"record":{"id":"9f3ba3e9fc9f7493","repo":"actualbudget/actual","slug":"file-name-matches-multiple-files-path","errorCode":null,"errorMessage":"File name matches multiple files: ${path}","messagePattern":"File name matches multiple files: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/importers/ynab4.ts","lineNumber":423,"sourceCode":"  unixFilepath = unixFilepath.replace(/\\.zip$/, '').replace(/.ynab4$/, '');\n\n  // Most budgets are named like \"Budget~51938D82.ynab4\" but sometimes\n  // they are only \"Budget.ynab4\". We only want to grab the name\n  // before the ~ if it exists.\n  const m = unixFilepath.match(/([^/~]+)[^/]*$/);\n  if (!m) {\n    return null;\n  }\n  return m[1];\n}\n\nfunction getFile(entries: string[], path: string) {\n  const files = entries.filter(e => e === path);\n  if (files.length === 0) {\n    throw new Error('Could not find file: ' + path);\n  }\n  if (files.length >= 2) {\n    throw new Error('File name matches multiple files: ' + path);\n  }\n  return files[0];\n}\n\nfunction join(...paths: string[]): string {\n  return paths.slice(1).reduce(\n    (full, path) => {\n      return full + '/' + path.replace(/^\\//, '');\n    },\n    paths[0].replace(/\\/$/, ''),\n  );\n}\n\nexport function parseFile(buffer: Buffer): YNAB4.YFull {\n  let zipped: Record<string, Uint8Array>;\n  try {\n    zipped = safeUnzip(buffer);\n  } catch (e) {","sourceCodeStart":405,"sourceCodeEnd":441,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/importers/ynab4.ts#L405-L441","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Re-export the budget from YNAB4 to get a clean archive with unique entry names.","Rebuild the zip from an extracted copy so each file appears once.","Inspect with unzip -l to find the duplicated entries and remove one when re-zipping."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function hasDuplicateEntries(buffer) {\n  const entries = Object.keys(safeUnzip(buffer));\n  return new Set(entries).size !== entries.length;\n}","typeGuard":"function isWellFormedZip(buffer) {\n  try {\n    const entries = Object.keys(safeUnzip(buffer));\n    return entries.length > 0 && new Set(entries).size === entries.length;\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  const budget = parseFile(buffer);\n} catch (e) {\n  if (e.message.startsWith('File name matches multiple files')) {\n    // rebuild the archive from an extracted copy\n  } else throw e;\n}","preventionTips":["Never concatenate or merge YNAB4 archives.","Re-zip only from a fully extracted directory so each entry is written once.","Check archives with unzip -l for duplicated names before import.","Prefer fresh exports from YNAB4 over repaired/converted archives."],"tags":["import","ynab4","zip","duplicate-entries"],"backgroundTag":"import-file-not-found","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}