Grouping by a categorical column with observed=False, the historical default, produces a row for every category defined on the dtype, not just the ones present in the data. Grouping by two categoricals produces the full cartesian product of their categories, which turns a small frame into millions of rows and looks like a memory leak.
The aggregated value for an absent group is NaN, or 0 for counts, so the output is not wrong so much as enormous. pandas 2.1 deprecated the default and pandas 3.0 makes observed=True the default, which returns only combinations that actually occur.
Pass observed=True explicitly today so the behaviour does not change under you at the 3.0 upgrade, and pass observed=False deliberately when you genuinely want the full grid, for example to build a dense pivot table. If a groupby suddenly produces far more rows than the input has unique keys, check the grouping column's dtype first: a category dtype created by astype("category") on a filtered frame keeps the categories from before the filter.