actualbudget/actual · error
Cleanup group name cannot be empty
Error message
Cleanup group name cannot be empty
What it means
resolveCleanupGroup normalizes a cleanup-group name for budget cleanup templates and rejects names that are empty after trimming. Cleanup groups must have a non-empty, unique (case-insensitive) name so rows can reference them. This guard prevents creating or resolving an unnamed group in the cleanup_groups table.
Source
Thrown at packages/loot-core/src/server/budget/cleanup-groups.ts:8
import { v4 as uuidv4 } from 'uuid';
import * as db from '#server/db';
export async function resolveCleanupGroup(name: string): Promise<string> {
const trimmed = name.trim();
if (trimmed.length === 0) {
throw new Error('Cleanup group name cannot be empty');
}
const key = trimmed.toLowerCase();
const existing = await db.first<{ id: string; tombstone: 0 | 1 }>(
`SELECT id, tombstone FROM cleanup_groups WHERE lower(name) = ? LIMIT 1`,
[key],
);
if (existing) {
if (existing.tombstone) {
await db.update('cleanup_groups', { id: existing.id, tombstone: 0 });
}
return existing.id;
}
const id = uuidv4();
await db.insertWithSchema('cleanup_groups', {
id,
name: trimmed,
tombstone: 0,
});View on GitHub (pinned to d4334cb6e6)
Solutions
- Provide a non-empty group name, e.g. resolveCleanupGroup('Groceries cap').
- Fix the template line to include the group name after the keyword.
- Trim/validate user input before calling; reject empty names in your UI or script.
- If the name comes from parsing, check the split logic isn't dropping the value.
Example fix
// before
await resolveCleanupGroup(groupNameFromNote); // '' when note is '#cleanup group:'
// after
if (!groupNameFromNote?.trim()) throw new Error('Cleanup group name required');
await resolveCleanupGroup(groupNameFromNote); Defensive patterns
Strategy: validation
Validate before calling
function isValidGroupName(name: unknown): name is string {
return typeof name === 'string' && name.trim().length > 0;
}
if (!isValidGroupName(rawName)) {
throw new Error('A non-empty cleanup group name is required');
}
await resolveCleanupGroup(rawName); Type guard
function isNonEmptyString(v: unknown): v is string {
return typeof v === 'string' && v.trim().length > 0;
} Try / catch
try {
const groupId = await resolveCleanupGroup(name);
} catch (e) {
if (e instanceof Error && e.message === 'Cleanup group name cannot be empty') {
logger.warn('Skipping cleanup rule with empty group name');
return null;
}
throw e;
} Prevention
- Trim and check group names in UI/forms before submitting
- Require a value after 'group' keywords in cleanup template notes
- Check string-splitting logic that extracts group names from notes
- Reject empty names early with clear user-facing messages
When it happens
Trigger: Calling resolveCleanupGroup('') or resolveCleanupGroup(' '), or a template/automation path that derives the group name from user input that ends up empty (e.g. `#cleanup group:` with nothing after the colon).
Common situations: Malformed cleanup template note with a missing group name, a form field left blank in tooling that calls the API, or string splitting that yields an empty token.
Related errors
- Invalid repo: must be in "owner/repo" format
- Invalid repo: must include both owner and repo name
- Unresolved cleanup group for overspend row: ${row.group}
- Unknown cleanup row type: ${String(row)}
- Invalid --name: must be a non-empty string.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/e0188f9309a00fdc.
Report an issue: GitHub.