{"record":{"id":"73a0b7f920473fb7","repo":"actualbudget/actual","slug":"getdocumentdir-id-is-falsy-id","errorCode":null,"errorMessage":"getDocumentDir: id is falsy: ${id}","messagePattern":"getDocumentDir: id is falsy: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/platform/server/fs/shared.ts","lineNumber":16,"sourceCode":"// @ts-strict-ignore\nimport { join } from '#platform/server/fs/path-join';\n\nlet documentDir;\nexport const _setDocumentDir = dir => (documentDir = dir);\n\nexport const getDocumentDir = () => {\n  if (!documentDir) {\n    throw new Error('Document directory is not set');\n  }\n  return documentDir;\n};\n\nexport const getBudgetDir = id => {\n  if (!id) {\n    throw new Error('getDocumentDir: id is falsy: ' + id);\n  }\n\n  // TODO: This should be better\n  //\n  // A cheesy safe guard. The id is generated from the budget name,\n  // so it provides an entry point for the user to accidentally (or\n  // intentionally) access other parts of the system. Always\n  // restrict it to only access files within the budget directory by\n  // never allowing slashes.\n  if (id.match(/[^A-Za-z0-9\\-_]/)) {\n    throw new Error(\n      `Invalid budget id \"${id}\". Check the id of your budget in the Advanced section of the settings page.`,\n    );\n  }\n\n  return join(getDocumentDir(), id);\n};\n","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/platform/server/fs/shared.ts#L1-L34","documentation":"getBudgetDir resolves the on-disk directory `<documentDir>/<budgetId>` and refuses to run when the budget id is falsy (undefined, null, empty string). This guards against silently writing budget files into the document root itself. Note the message text says 'getDocumentDir' for historical reasons but it comes from getBudgetDir.","triggerScenarios":"Calling getBudgetDir(undefined/null/'') — typically when a budget id failed to load, a caller passed an unset variable, or a remote-id lookup returned nothing before the directory is computed.","commonSituations":"A budget list returned no id for the requested budget; migration/import code computing a directory before the id is assigned; tests constructing budgets without ids; a stale UI passing a cleared id after budget close.","solutions":["Validate the budget id is a non-empty string before calling any API that resolves a budget directory.","Trace where the id comes from (budget list, localStorage, query param) and fix the source returning an empty value.","Only call getBudgetDir after a budget has been explicitly loaded/created so a valid id exists."],"exampleFix":"// before\nconst dir = getBudgetDir(budget?.id);\n\n// after\nif (!budget?.id) {\n  throw new Error('No budget loaded: cannot resolve budget directory');\n}\nconst dir = getBudgetDir(budget.id);","handlingStrategy":"validation","validationCode":"function assertBudgetId(id) {\n  if (typeof id !== 'string' || id.length === 0) {\n    throw new Error(`Budget id must be a non-empty string, got: ${JSON.stringify(id)}`);\n  }\n}","typeGuard":"function isBudgetId(id: unknown): id is string {\n  return typeof id === 'string' && id.length > 0;\n}","tryCatchPattern":"try {\n  const dir = getBudgetDir(id);\n} catch (e) {\n  if (String(e.message).includes('id is falsy')) {\n    // no budget loaded — prompt user to open/create one\n  } else throw e;\n}","preventionTips":["Only call budget-path APIs while a budget is loaded.","Guard optional ids (budget?.id) at call sites.","Load budget ids from a single source of truth and validate on read."],"tags":["validation","filesystem","null-check","server"],"backgroundTag":"missing-required-argument","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}