actualbudget/actual · error

Invalid tag name

Error message

Invalid tag name

What it means

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').

Source

Thrown at packages/loot-core/src/server/tags/app.ts:122

async function updateTag(
  tag: Partial<TagEntity> & Pick<TagEntity, 'id'>,
): Promise<Partial<TagEntity>> {
  const { hidden, ...rest } = tag;
  await db.updateTag({
    ...rest,
    ...(hidden !== undefined ? { hidden: hidden ? 1 : 0 } : {}),
  });
  return tag;
}

async function renameTag({
  id,
  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 });

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Trim the input and reject names that are empty, contain whitespace, or contain '#' before calling renameTag
  2. Strip a leading '#' from user-entered tag strings in the UI before validating
  3. Show inline validation matching the same regex so the user never submits an invalid name

Example fix

// before
await renameTag({ id, tag: '#groceries ' }); // Error: Invalid tag name
// after
const name = raw.replace(/^#+/, '').trim();
if (!/^[^#\s]+$/.test(name)) throw new Error('Invalid tag name');
await renameTag({ id, tag: name });
Defensive patterns

Strategy: validation

Validate before calling

function isValidTagName(name: string): boolean {
  return /^[^#\s]+$/.test(name.trim());
}

Type guard

function isInvalidTagNameError(e: unknown): boolean {
  return e instanceof Error && e.message === 'Invalid tag name';
}

Try / catch

try {
  await renameTag({ id, tag });
} catch (e) {
  if (isInvalidTagNameError(e)) {
    notifyUser('Tags cannot contain spaces or # and cannot be empty');
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/de19ea71454c734b. Report an issue: GitHub.