actualbudget/actual · error
Category with id ${id} not found.
Error message
Category with id ${id} not found. What it means
deleteCategory first fetches the category row to determine whether it is an income category; if no row exists for the given id, it throws instead of silently deleting nothing.
Source
Thrown at packages/loot-core/src/server/budget/app.ts:381
await batchMessages(async () => {
await db.moveCategory(id, groupId, targetId);
});
}
async function deleteCategory({
id,
transferId,
}: {
id: CategoryEntity['id'];
transferId?: CategoryEntity['id'] | null;
}): Promise<void> {
await batchMessages(async () => {
const row = await db.first<Pick<db.DbCategory, 'is_income'>>(
'SELECT is_income FROM categories WHERE id = ?',
[id],
);
if (!row) {
throw new Error(`Category with id ${id} not found.`);
}
const transfer =
transferId &&
(await db.first<Pick<db.DbCategory, 'is_income'>>(
'SELECT is_income FROM categories WHERE id = ?',
[transferId],
));
if (transferId && !transfer) {
throw new Error(`Transfer category with id ${transferId} not found.`);
} else if (
transferId &&
row &&
transfer &&
row.is_income !== transfer.is_income
) {
throw new Error('Cannot transfer between income and expense categories.');View on GitHub (pinned to d4334cb6e6)
Solutions
- Verify the category id exists (q('categories').filter({ id }).select('*')) before deleting.
- Refresh local state/sync and retry so stale ids are replaced.
- Guard the UI delete action against double-submission.
Example fix
// before
await deleteCategory(catId); // catId may be stale
// after
const rows = await q('categories').filter({ id: catId }).select('*');
if (rows.length > 0) await deleteCategory(catId); Defensive patterns
Strategy: validation
Validate before calling
const cat = await q('categories').filter({ id }).select('*').execute();
if (cat.length === 0) throw new Error(`Cannot delete: category ${id} not found`); Type guard
function categoryExists(row: { id: string } | null | undefined): boolean {
return row != null;
} Try / catch
try {
await app.deleteCategory(id);
} catch (e) {
if (e.message.includes('not found')) {
refreshCategories(); // resync to clear stale ids
} else throw e;
} Prevention
- Disable delete buttons for stale/unsynced rows
- Refresh category state after every sync before performing mutations
- Ensure you pass a category id, not a category-group id
When it happens
Trigger: Calling app.deleteCategory(id) with an id that is not in the categories table (already deleted, wrong id, or id from a different budget file).
Common situations: Stale client state after a sync; deleting the same category twice from racing UI actions; using a group id instead of a category id.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Transfer category with id ${transferId} not found.
- A category with the name "${category.name}" already exists.
- Cannot transfer between income and expense categories.
- ${debug}: category "${id}" does not exist
- Error importing budget: ${result.error}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/c0e7ce33a74c70f3.
Report an issue: GitHub.