{"record":{"id":"c0e7ce33a74c70f3","repo":"actualbudget/actual","slug":"category-with-id-id-not-found","errorCode":null,"errorMessage":"Category with id ${id} not found.","messagePattern":"Category with id (.+?) not found\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/budget/app.ts","lineNumber":381,"sourceCode":"  await batchMessages(async () => {\n    await db.moveCategory(id, groupId, targetId);\n  });\n}\n\nasync function deleteCategory({\n  id,\n  transferId,\n}: {\n  id: CategoryEntity['id'];\n  transferId?: CategoryEntity['id'] | null;\n}): Promise<void> {\n  await batchMessages(async () => {\n    const row = await db.first<Pick<db.DbCategory, 'is_income'>>(\n      'SELECT is_income FROM categories WHERE id = ?',\n      [id],\n    );\n    if (!row) {\n      throw new Error(`Category with id ${id} not found.`);\n    }\n\n    const transfer =\n      transferId &&\n      (await db.first<Pick<db.DbCategory, 'is_income'>>(\n        'SELECT is_income FROM categories WHERE id = ?',\n        [transferId],\n      ));\n\n    if (transferId && !transfer) {\n      throw new Error(`Transfer category with id ${transferId} not found.`);\n    } else if (\n      transferId &&\n      row &&\n      transfer &&\n      row.is_income !== transfer.is_income\n    ) {\n      throw new Error('Cannot transfer between income and expense categories.');","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/budget/app.ts#L363-L399","documentation":"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.","triggerScenarios":"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).","commonSituations":"Stale client state after a sync; deleting the same category twice from racing UI actions; using a group id instead of a category id.","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."],"exampleFix":"// before\nawait deleteCategory(catId); // catId may be stale\n// after\nconst rows = await q('categories').filter({ id: catId }).select('*');\nif (rows.length > 0) await deleteCategory(catId);","handlingStrategy":"validation","validationCode":"const cat = await q('categories').filter({ id }).select('*').execute();\nif (cat.length === 0) throw new Error(`Cannot delete: category ${id} not found`);","typeGuard":"function categoryExists(row: { id: string } | null | undefined): boolean {\n  return row != null;\n}","tryCatchPattern":"try {\n  await app.deleteCategory(id);\n} catch (e) {\n  if (e.message.includes('not found')) {\n    refreshCategories(); // resync to clear stale ids\n  } else throw e;\n}","preventionTips":["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"],"tags":["budget","category","not-found"],"backgroundTag":"record-not-found","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}