{"record":{"id":"397dabd64ad3f1b9","repo":"actualbudget/actual","slug":"a-tag-with-that-name-already-exists","errorCode":null,"errorMessage":"A tag with that name already exists","messagePattern":"A tag with that name already exists","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/tags/app.ts","lineNumber":135,"sourceCode":"  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\n    for (const { id: transactionId, notes } of await db.findTags()) {\n      const renamed = renameTagInNotes(notes, existing.tag, name);\n      if (renamed !== notes) {\n        await db.updateTransaction({ id: transactionId, notes: renamed });\n      }\n    }\n  });\n\n  return id;\n}\n\nasync function discoverTags(): Promise<TagEntity[]> {\n  const taggedNotes = await db.findTags();","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/tags/app.ts#L117-L153","documentation":"renameTag() enforces tag-name uniqueness: if any other tag (different id) already uses the target name, it throws Error('A tag with that name already exists'). Renaming a tag to its current name is a no-op that returns the id early.","triggerScenarios":"renameTag({ id, tag }) targets a name present in db.getAllTags() under a different id — e.g. renaming 'food' to 'groceries' when 'groceries' already exists.","commonSituations":"Merging two similar tags by renaming one onto the other; users typing a name that already exists; automated scripts iterating renames that collide.","solutions":["Check existing tag names and reject/normalize duplicates in the UI before calling renameTag","If the intent is a merge, use merge/collapse functionality or manually re-point notes instead of renaming onto an existing name","Catch the error and surface 'name already in use' to the user"],"exampleFix":"// before\nawait renameTag({ id, tag: 'groceries' }); // Error: already exists\n// after\nconst all = await getAllTags();\nif (all.some(t => t.id !== id && t.tag === 'groceries')) {\n  throw new Error('A tag with that name already exists');\n}\nawait renameTag({ id, tag: 'groceries' });","handlingStrategy":"validation","validationCode":"const all = await getAllTags();\nif (all.some(t => t.id !== id && t.tag === name.trim())) {\n  throw new Error('A tag with that name already exists');\n}","typeGuard":"function isDuplicateTagName(e: unknown): boolean {\n  return e instanceof Error &&\n    e.message === 'A tag with that name already exists';\n}","tryCatchPattern":"try {\n  await renameTag({ id, tag });\n} catch (e) {\n  if (isDuplicateTagName(e)) {\n    notifyUser('That tag name is already in use');\n  } else throw e;\n}","preventionTips":["Check name availability in the UI as the user types","Use autocomplete on existing tag names","Catch the error and keep the editor open with the message"],"tags":["tags","validation","uniqueness"],"backgroundTag":"duplicate-resource-name","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}