actualbudget/actual · error

Tag not found

Error message

Tag not found

What it means

renameTag() looks up the tag by id via db.getTags() and throws Error('Tag not found') when no existing tag has that id. This is an existence check before applying a rename, preventing updates to deleted or never-existing tags.

Source

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

  });
  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 });

    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 });
      }
    }
  });

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Re-fetch the tag list before renaming and confirm the id still exists
  2. Handle deletion of the tag gracefully in the UI instead of attempting a rename
  3. If ids come from another device, re-sync first so the local tag set is current

Example fix

// before
await renameTag({ id: staleId, tag: 'new' }); // Error: Tag not found
// after
const tags = await getTags();
if (tags.some(t => t.id === id)) await renameTag({ id, tag: 'new' });
Defensive patterns

Strategy: validation

Validate before calling

const tags = await getTags();
if (!tags.some(t => t.id === id)) {
  throw new Error(`Tag ${id} does not exist; refresh and retry`);
}

Type guard

function isTagNotFound(e: unknown): boolean {
  return e instanceof Error && e.message === 'Tag not found';
}

Try / catch

try {
  await renameTag({ id, tag });
} catch (e) {
  if (isTagNotFound(e)) {
    await refreshTagList(); // drop stale id from UI
  } else throw e;
}

Prevention

When it happens

Trigger: renameTag({ id, tag }) is called with an id that no longer exists (tag deleted elsewhere/concurrently), a stale id from an outdated UI list, or a malformed/unknown id.

Common situations: Two tabs/devices where one deletes the tag while the other renames it; UI retaining stale state after deletion; API/plugin code passing a wrong id.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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