{"record":{"id":"de19ea71454c734b","repo":"actualbudget/actual","slug":"invalid-tag-name","errorCode":null,"errorMessage":"Invalid tag name","messagePattern":"Invalid tag name","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/tags/app.ts","lineNumber":122,"sourceCode":"async function updateTag(\n  tag: Partial<TagEntity> & Pick<TagEntity, 'id'>,\n): Promise<Partial<TagEntity>> {\n  const { hidden, ...rest } = tag;\n  await db.updateTag({\n    ...rest,\n    ...(hidden !== undefined ? { hidden: hidden ? 1 : 0 } : {}),\n  });\n  return tag;\n}\n\nasync function renameTag({\n  id,\n  tag: newTag,\n}: Pick<TagEntity, 'id' | 'tag'>): Promise<TagEntity['id']> {\n  const name = newTag.trim();\n  // accept any char except whitespaces and '#', same as tag creation\n  if (!/^[^#\\s]+$/.test(name)) {\n    throw new Error('Invalid tag name');\n  }\n\n  const tags = await db.getTags();\n  const allTags = await db.getAllTags();\n  const existing = tags.find(t => t.id === id);\n  if (!existing) {\n    throw new Error('Tag not found');\n  }\n  if (existing.tag === name) {\n    return id;\n  }\n  if (allTags.some(t => t.id !== id && t.tag === name)) {\n    throw new Error('A tag with that name already exists');\n  }\n\n  await batchMessages(async () => {\n    await db.updateTag({ id, tag: name });\n","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/tags/app.ts#L104-L140","documentation":"renameTag() validates the new tag name before touching the database: after trimming, the name must match /^[^#\\s]+$/ (at least one character, no whitespace, no leading '#'). Empty names, names containing spaces, or names containing '#' throw this plain Error('Invalid tag name').","triggerScenarios":"Calling renameTag({ id, tag: newTag }) with a tag that trims to empty string, contains whitespace (e.g. 'my tag'), or contains '#' (e.g. '#food') — the same rule as tag creation.","commonSituations":"Passing a UI string that still includes the '#' prefix; copying a tag with a trailing newline or spaces that trim to ''; programmatic renames that don't pre-sanitize user input.","solutions":["Trim the input and reject names that are empty, contain whitespace, or contain '#' before calling renameTag","Strip a leading '#' from user-entered tag strings in the UI before validating","Show inline validation matching the same regex so the user never submits an invalid name"],"exampleFix":"// before\nawait renameTag({ id, tag: '#groceries ' }); // Error: Invalid tag name\n// after\nconst name = raw.replace(/^#+/, '').trim();\nif (!/^[^#\\s]+$/.test(name)) throw new Error('Invalid tag name');\nawait renameTag({ id, tag: name });","handlingStrategy":"validation","validationCode":"function isValidTagName(name: string): boolean {\n  return /^[^#\\s]+$/.test(name.trim());\n}","typeGuard":"function isInvalidTagNameError(e: unknown): boolean {\n  return e instanceof Error && e.message === 'Invalid tag name';\n}","tryCatchPattern":"try {\n  await renameTag({ id, tag });\n} catch (e) {\n  if (isInvalidTagNameError(e)) {\n    notifyUser('Tags cannot contain spaces or # and cannot be empty');\n  } else throw e;\n}","preventionTips":["Strip leading '#' and trim input in the UI before validating","Reuse the same regex as the library for client-side checks","Reject empty/whitespace-only names early","Document the naming rule next to the input field"],"tags":["validation","tags"],"backgroundTag":"invalid-tag-name","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}