actualbudget/actual · error
A tag with that name already exists
Error message
A tag with that name already exists
What it means
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.
Source
Thrown at packages/loot-core/src/server/tags/app.ts:135
tag: newTag,
}: Pick<TagEntity, 'id' | 'tag'>): Promise<TagEntity['id']> {
const name = newTag.trim();
// accept any char except whitespaces and '#', same as tag creation
if (!/^[^#\s]+$/.test(name)) {
throw new Error('Invalid tag name');
}
const tags = await db.getTags();
const allTags = await db.getAllTags();
const existing = tags.find(t => t.id === id);
if (!existing) {
throw new Error('Tag not found');
}
if (existing.tag === name) {
return id;
}
if (allTags.some(t => t.id !== id && t.tag === name)) {
throw new Error('A tag with that name already exists');
}
await batchMessages(async () => {
await db.updateTag({ id, tag: name });
for (const { id: transactionId, notes } of await db.findTags()) {
const renamed = renameTagInNotes(notes, existing.tag, name);
if (renamed !== notes) {
await db.updateTransaction({ id: transactionId, notes: renamed });
}
}
});
return id;
}
async function discoverTags(): Promise<TagEntity[]> {
const taggedNotes = await db.findTags();View on GitHub (pinned to d4334cb6e6)
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
Example fix
// before
await renameTag({ id, tag: 'groceries' }); // Error: already exists
// after
const all = await getAllTags();
if (all.some(t => t.id !== id && t.tag === 'groceries')) {
throw new Error('A tag with that name already exists');
}
await renameTag({ id, tag: 'groceries' }); Defensive patterns
Strategy: validation
Validate before calling
const all = await getAllTags();
if (all.some(t => t.id !== id && t.tag === name.trim())) {
throw new Error('A tag with that name already exists');
} Type guard
function isDuplicateTagName(e: unknown): boolean {
return e instanceof Error &&
e.message === 'A tag with that name already exists';
} Try / catch
try {
await renameTag({ id, tag });
} catch (e) {
if (isDuplicateTagName(e)) {
notifyUser('That tag name is already in use');
} else throw e;
} Prevention
- 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
When it happens
Trigger: renameTag({ id, tag }) targets a name present in db.getAllTags() under a different id — e.g. renaming 'food' to 'groceries' when 'groceries' already exists.
Common situations: Merging two similar tags by renaming one onto the other; users typing a name that already exists; automated scripts iterating renames that collide.
Related errors
- Invalid tag name
- There is already a schedule named: ${newName}
- Invalid --name: must be a non-empty string.
- No update fields provided. Use --name or --offbudget.
- Invalid cutoff date: expected a valid date (e.g. YYYY-MM-DD)
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/397dabd64ad3f1b9.
Report an issue: GitHub.