{"record":{"id":"3127d0354b6d0c2f","repo":"actualbudget/actual","slug":"cannot-delete-rule-invalid-id","errorCode":null,"errorMessage":"Cannot delete rule: invalid id","messagePattern":"Cannot delete rule: invalid id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/mobile/rules/MobileRuleEditPage.tsx","lineNumber":112,"sourceCode":"\n  const handleSave = () => {\n    if (rule?.id) {\n      showUndoNotification({\n        message: t('Rule saved successfully'),\n      });\n    }\n    // Navigate back to rules list\n    void navigate('/rules');\n  };\n\n  const handleCancel = () => {\n    void navigate(-1);\n  };\n\n  const handleDelete = () => {\n    // Runtime guard to ensure id exists\n    if (!id || id === 'new') {\n      throw new Error('Cannot delete rule: invalid id');\n    }\n\n    dispatch(\n      pushModal({\n        modal: {\n          name: 'confirm-delete',\n          options: {\n            message: t('Are you sure you want to delete this rule?'),\n            onConfirm: async () => {\n              try {\n                await send('rule-delete', id);\n                showUndoNotification({\n                  message: t('Rule deleted successfully'),\n                });\n                void navigate('/rules');\n              } catch (error) {\n                console.error('Failed to delete rule:', error);\n                dispatch(","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/mobile/rules/MobileRuleEditPage.tsx#L94-L130","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst handleDelete = () => {\n  if (!id || id === 'new') {\n    throw new Error('Cannot delete rule: invalid id');\n  }\n// after\nconst handleDelete = () => {\n  if (!id || id === 'new') {\n    void navigate(-1); // nothing persisted to delete\n    return;\n  }","handlingStrategy":"validation","validationCode":"const canDelete = Boolean(id) && id !== 'new';\nif (!canDelete) {\n  void navigate(-1);\n  return;\n}\n// safe to open delete confirmation","typeGuard":"function isPersistedRuleId(id: string | undefined): id is string {\n  return typeof id === 'string' && id !== '' && id !== 'new';\n}","tryCatchPattern":"try {\n  handleDelete();\n} catch (err) {\n  if (String(err).includes('invalid id')) {\n    void navigate(-1); // nothing to delete\n  } else {\n    throw err;\n  }\n}","preventionTips":["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."],"tags":["react","routing","guard-clause","mobile"],"backgroundTag":"invalid-entity-id","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}