{"record":{"id":"6fb20d2ff8907573","repo":"actualbudget/actual","slug":"could-not-find-file-path","errorCode":null,"errorMessage":"Could not find file: ${path}","messagePattern":"Could not find file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/importers/ynab4.ts","lineNumber":420,"sourceCode":"    return null;\n  }\n\n  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>;","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/importers/ynab4.ts#L402-L438","documentation":"When importing a YNAB4 (.ynab4) budget zip, parseFile unzips the archive and getFile(entries, path) looks up an entry whose name exactly equals the requested path. If no zip entry matches exactly, this error is thrown. It almost always means the archive has a different internal directory layout than the importer expects (e.g. an extra or missing root folder like 'Budget~.../*.ynab4/').","triggerScenarios":"Calling parseFile(buffer) on a YNAB4 zip whose entries, after root-directory stripping, do not contain an entry exactly equal to the computed yfullPath or other requested file (Budget.yfull, payees, etc.), e.g. because the zip lacks the top-level *.ynab4 directory or uses differing case/extra nesting.","commonSituations":"Re-exported or manually re-zipped YNAB4 backups where the folder structure changed; macOS/Windows zip tools adding an extra wrapper folder; renamed budget directory inside the archive; importing a YNAB5 (nYab) file through the YNAB4 importer path.","solutions":["Re-zip the budget so the archive contains the original structure: a top-level folder ending in .ynab4 containing Budget.yfull and the data files, with no extra wrapper folders.","Inspect the zip contents (unzip -l budget.ynab4) and confirm Budget.yfull exists at the expected path; fix casing/nesting to match exactly.","Verify you are importing a YNAB4 export, not a YNAB5 (.yfull vs nYab format); use the correct importer for the format.","Extract the zip, confirm the file exists and is not corrupted, then re-zip without compression changes to the inner structure."],"exampleFix":"// before (broken zip layout)\nBudget.yfull\nData/payees.yfull\n// after (expected YNAB4 layout)\nMyBudget~9C1A.ynab4/Budget.yfull\nMyBudget~9C1A.ynab4/Data/payees.yfull","handlingStrategy":"validation","validationCode":"import { unzipSync } from 'fflate';\nfunction canParseYnab4(buffer) {\n  try {\n    const entries = Object.keys(safeUnzip(buffer));\n    return entries.some(e => /[^/]*\\.ynab4/.test(e)) &&\n      entries.some(e => e.endsWith('Budget.yfull'));\n  } catch { return false; }\n}","typeGuard":"function isYnab4Zip(buffer) {\n  return Buffer.isBuffer(buffer) &&\n    buffer.length > 4 &&\n    buffer.subarray(0, 2).toString('latin1') === 'PK';\n}","tryCatchPattern":"try {\n  const budget = parseFile(buffer);\n} catch (e) {\n  if (e.message.startsWith('Could not find file')) {\n    // inspect zip layout, re-export/re-zip the budget\n  } else throw e;\n}","preventionTips":["Import only archives exported directly by YNAB4, never re-zipped folders.","Run unzip -l budget.ynab4 and confirm a top-level *.ynab4 folder containing Budget.yfull before importing.","Check for extra wrapper folders added by OS zip tools and remove them.","Use the YNAB5 importer for nYab exports; do not feed them to the YNAB4 path."],"tags":["import","ynab4","zip","file-not-found"],"backgroundTag":"import-file-not-found","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}