actualbudget/actual · error

Internal error: category ${categoryIdToMove} is not in a gro

Error message

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

What it means

IncomeCategoryList throws this when the dragged category exists but has no `group` reference, which is required to compute its reorder position. A group-less income category is treated as invalid internal state that cannot be safely moved.

Source

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

              borderRadius: 4,
            },
          })}
        />
      );
    },
    onReorder: e => {
      const [key] = e.keys;
      const categoryIdToMove = key as CategoryEntity['id'];
      const categoryToMove = categories.find(c => c.id === categoryIdToMove);

      if (!categoryToMove) {
        throw new Error(
          `Internal error: category with ID ${categoryIdToMove} not found.`,
        );
      }

      if (!categoryToMove.group) {
        throw new Error(
          `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) {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Ensure the categories query includes each category's group relation.
  2. Assign the orphan category to an income group before reordering.
  3. Validate category/group integrity after import or sync.
  4. Skip group-less categories during reorder instead of throwing.

Example fix

// before
if (!categoryToMove.group) {
  throw new Error(`Internal error: category ${categoryIdToMove} is not in a group and cannot be moved.`);
}
// after
if (!categoryToMove.group) {
  console.warn(`Category ${categoryIdToMove} has no group; skipping reorder`);
  return;
}
Defensive patterns

Strategy: validation

Validate before calling

if (!categoryToMove?.group) {
  console.warn(`Category ${categoryIdToMove} has no group; skipping reorder`);
  return;
}

Type guard

function hasGroup(c: CategoryEntity): c is CategoryEntity & { group: NonNullable<CategoryEntity['group']> } {
  return Boolean(c.group);
}

Try / catch

try {
  onReorder(e);
} catch (err) {
  if (String(err).includes('not in a group')) {
    refreshCategories();
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: `onReorder` is called with a category whose `categoryToMove.group` is undefined/null — the category was loaded without its group relation or has no group in the data model.

Common situations: Import/sync produced categories without group assignments; the data query omitted the group relation; a programmatically created category lacks a group ID.

Related errors


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