{"record":{"id":"97c710b9f082eb85","repo":"actualbudget/actual","slug":"message","errorCode":null,"errorMessage":"${message}","messagePattern":"\\$\\{message\\}","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/budgetfiles/app.ts","lineNumber":329,"sourceCode":"    }\n  }\n\n  return 'ok';\n}\n\nasync function duplicateBudget({\n  id,\n  newName,\n  cloudSync,\n  open,\n}: {\n  id: Budget['id'];\n  newName: Budget['name'];\n  cloudSync: boolean;\n  open: 'none' | 'original' | 'copy';\n}): Promise<Budget['id']> {\n  const { valid, message } = await validateBudgetName(newName);\n  if (!valid) throw new Error(message);\n\n  const budgetDir = fs.getBudgetDir(id);\n\n  const newId = await idFromBudgetName(newName);\n\n  // copy metadata from current budget\n  // replace id with new budget id and budgetName with new budget name\n  const metadataText = await fs.readFile(fs.join(budgetDir, 'metadata.json'));\n  const metadata = JSON.parse(metadataText);\n  metadata.id = newId;\n  metadata.budgetName = newName;\n  [\n    'cloudFileId',\n    'groupId',\n    'lastUploaded',\n    'encryptKeyId',\n    'lastSyncedTimestamp',\n  ].forEach(item => {","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/budgetfiles/app.ts#L311-L347","documentation":"`duplicateBudget` first validates the requested new budget name via `validateBudgetName` before doing any filesystem work. If validation fails, it re-throws the validator's human-readable `message` verbatim. Typical failures are empty/whitespace names, names containing path-hostile characters, or a name that already collides with an existing budget directory (mapping to an existing budget id via `idFromBudgetName`).","triggerScenarios":"Calling `duplicateBudget({ id, newName, ... })` through the server/app API where `newName` is empty, contains invalid characters (e.g. `/`, `\\`, leading dots), or normalizes to a budget id that already exists on disk.","commonSituations":"A UI lets the user submit the duplicate dialog with a blank name; an automation script passes an unsanitized filename; renaming conflicts with an existing budget because Actual derives the budget id from the name; OS-specific forbidden characters (colon on Windows, etc.).","solutions":["Read the thrown `message` — it comes from `validateBudgetName` and states the exact rule violated; fix `newName` accordingly.","Ensure `newName` is a non-empty, trimmed string without path separators or OS-reserved characters.","Check whether a budget with that name already exists (list budgets via the sync-server/budget files) and pick a unique name.","Wrap the call in a try/catch and surface `error.message` to the user in the rename/duplicate dialog instead of failing silently."],"exampleFix":"// before\nawait duplicateBudget({ id, newName: '  ', cloudSync: false, open: 'none' });\n\n// after\nconst newName = 'Budget 2026 Copy';\nif (newName.trim().length > 0) {\n  await duplicateBudget({ id, newName, cloudSync: false, open: 'none' });\n}","handlingStrategy":"try-catch","validationCode":"function validateBudgetNameClient(name) {\n  if (typeof name !== 'string' || name.trim().length === 0) return 'Budget name is required';\n  if (/[\\\\/:*?\"<>|]/.test(name)) return 'Budget name contains invalid characters';\n  return null; // ok\n}","typeGuard":"function isValidBudgetName(name) {\n  return typeof name === 'string' && name.trim().length > 0 && !/[\\\\/:*?\"<>|]/.test(name);\n}","tryCatchPattern":"try {\n  await duplicateBudget({ id, newName, cloudSync: false, open: 'none' });\n} catch (e) {\n  showValidationError(e.message); // message comes from validateBudgetName\n}","preventionTips":["Always trim and non-empty-check the new name before calling","Strip or reject path-hostile characters (/, \\, :, and OS-reserved ones)","Check for an existing budget with the same name/id before duplicating","Surface e.message directly to the user in the duplicate dialog — it is already user-readable"],"tags":["budget","validation","naming"],"backgroundTag":"invalid-budget-name","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}