actualbudget/actual · error

Internal error: category with ID ${targetCategoryId} not fou

Error message

Internal error: category with ID ${targetCategoryId} not found.

What it means

IncomeCategoryList throws this while handling a drag-and-drop category reorder: after a drop event it searches the local `categories` array for the drop target's ID and throws when `findIndex` returns -1. It is an invariant assertion meaning the UI's drag payload referenced a category that is no longer present in the rendered category list. Actual Budget throws it to fail fast rather than silently corrupt the category ordering.

Source

Thrown at packages/desktop-client/src/components/mobile/budget/IncomeCategoryList.tsx:83

          `Internal error: category ${categoryIdToMove} is not in a group and cannot be moved.`,
        );
      }

      const targetCategoryId = e.target.key as CategoryEntity['id'];

      if (e.target.dropPosition === 'before') {
        moveCategory.mutate({
          id: categoryToMove.id,
          groupId: categoryToMove.group,
          targetId: targetCategoryId,
        });
      } else if (e.target.dropPosition === 'after') {
        const targetCategoryIndex = categories.findIndex(
          c => c.id === targetCategoryId,
        );

        if (targetCategoryIndex === -1) {
          throw new Error(
            `Internal error: category with ID ${targetCategoryId} not found.`,
          );
        }

        const nextToTargetCategory = categories[targetCategoryIndex + 1];

        moveCategory.mutate({
          id: categoryToMove.id,
          groupId: categoryToMove.group,
          // Due to the way `moveCategory` works, we use the category next to the
          // actual target category here because `moveCategory` always shoves the
          // category *before* the target category.
          // On the other hand, using `null` as `targetId` moves the category
          // to the end of the list.
          targetId: nextToTargetCategory?.id || null,
        });
      }
    },

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Refresh the category list / reload the budget so the local `categories` array matches the server, then retry the drag.
  2. Check that the drag handler passes the target category's real `id` (not a group id or display index).
  3. Guard the reorder: if `targetCategoryIndex === -1`, log and return instead of rethrowing in production.
  4. If reproducible, file a bug with the category IDs involved — it indicates state desync between the drag layer and category state.

Example fix

// before
const targetCategoryIndex = categories.findIndex(c => c.id === targetCategoryId);
if (targetCategoryIndex === -1) {
  throw new Error(`Internal error: category with ID ${targetCategoryId} not found.`);
}
// after
const targetCategoryIndex = categories.findIndex(c => c.id === targetCategoryId);
if (targetCategoryIndex === -1) {
  console.warn('Drop target category no longer exists:', targetCategoryId);
  return; // gracefully ignore stale drag
}
Defensive patterns

Strategy: validation

Validate before calling

// before initiating/consuming a drag on a category
const exists = categories.some(c => c.id === targetCategoryId);
if (!exists) {
  refreshCategories(); // reload state before allowing the drop
  return;
}

Type guard

function isKnownCategory(id: string, categories: { id: string }[]): boolean {
  return categories.some(c => c.id === id);
}

Try / catch

try {
  onDrop(e);
} catch (err) {
  if (String(err).includes('not found')) {
    refreshCategories();
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: A drag-and-drop `onDrop`-style handler fires with `e.target.dropPosition === 'after'` and the drop target's `targetCategoryId` does not exist in the `categories` array in state — e.g. the category was deleted or hidden by another action/device while the drag was in flight, or a stale drag payload references an old category ID.

Common situations: Users dragging a category onto another category that a concurrent sync or undo just removed; a stale mobile UI after category deletion on another device; a bug where the drag source passes a groupId or transient ID instead of a real category id.

Related errors


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