actualbudget/actual · error
Cannot delete rule: invalid id
Error message
Cannot delete rule: invalid id
What it means
MobileRuleEditPage's handleDelete guard throws when the route-supplied rule `id` is missing or equals the literal 'new'. Deleting only makes sense for a persisted rule, so the code defends against the edit page being opened in create mode (id 'new') or without an id and then the user tapping Delete. It is a defensive runtime guard for the modal-flow navigation.
Source
Thrown at packages/desktop-client/src/components/mobile/rules/MobileRuleEditPage.tsx:112
const handleSave = () => {
if (rule?.id) {
showUndoNotification({
message: t('Rule saved successfully'),
});
}
// Navigate back to rules list
void navigate('/rules');
};
const handleCancel = () => {
void navigate(-1);
};
const handleDelete = () => {
// Runtime guard to ensure id exists
if (!id || id === 'new') {
throw new Error('Cannot delete rule: invalid id');
}
dispatch(
pushModal({
modal: {
name: 'confirm-delete',
options: {
message: t('Are you sure you want to delete this rule?'),
onConfirm: async () => {
try {
await send('rule-delete', id);
showUndoNotification({
message: t('Rule deleted successfully'),
});
void navigate('/rules');
} catch (error) {
console.error('Failed to delete rule:', error);
dispatch(View on GitHub (pinned to d4334cb6e6)
Solutions
- Only render/enable the Delete action when a persisted rule id exists (id present and !== 'new').
- If in create mode ('new'), delete should just navigate back — fix the entry point that shows Delete for unsaved rules.
- Verify the route/navigation code passes the rule id param correctly to MobileRuleEditPage.
- Wrap the delete flow in a check so users get a friendly message instead of a crash.
Example fix
// before
const handleDelete = () => {
if (!id || id === 'new') {
throw new Error('Cannot delete rule: invalid id');
}
// after
const handleDelete = () => {
if (!id || id === 'new') {
void navigate(-1); // nothing persisted to delete
return;
} Defensive patterns
Strategy: validation
Validate before calling
const canDelete = Boolean(id) && id !== 'new';
if (!canDelete) {
void navigate(-1);
return;
}
// safe to open delete confirmation Type guard
function isPersistedRuleId(id: string | undefined): id is string {
return typeof id === 'string' && id !== '' && id !== 'new';
} Try / catch
try {
handleDelete();
} catch (err) {
if (String(err).includes('invalid id')) {
void navigate(-1); // nothing to delete
} else {
throw err;
}
} Prevention
- Disable/hide destructive actions for unsaved ('new') entities.
- Type route params as `id: string | undefined` and narrow before use.
- Deep-link tests should cover the create-mode route exercising the delete button.
When it happens
Trigger: User taps the Delete button on the mobile rule edit page while the route param `id` is undefined or 'new' — i.e. the page was opened to create a rule, or navigation happened without an id.
Common situations: Deep-linking or navigating to the rule edit page without an id; a stale UI entry point that opens the edit page in create mode; a routing bug after upgrading the app where the delete button is rendered in 'new' mode.
Related errors
- Transaction ${transactionId} not found
- InitialFocus expects a single valid React element as its chi
- Unknown budget action type: ${String(type)}
- Unknown display type: ${String(type satisfies never)}
- Unhandled action type: ${action.type}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/3127d0354b6d0c2f.
Report an issue: GitHub.