{"record":{"id":"f3b4eff49b787a96","repo":"apache/superset","slug":"clienterror-message-clienterror-error-t-sor-f3b4ef","errorCode":null,"errorMessage":"clientError.message || clientError.error || t('Sorry, an error occurred')","messagePattern":"clientError\\.message \\|\\| clientError\\.error \\|\\| t\\('Sorry, an error occurred'\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"superset-frontend/src/hooks/useUnsavedChangesPrompt/index.ts","lineNumber":66,"sourceCode":"\n  const confirmNavigationRef = useRef<(() => void) | null>(null);\n  const unblockRef = useRef<() => void>(() => {});\n  const manualSaveRef = useRef(false); // Track if save was user-initiated (not via navigation)\n\n  const handleConfirmNavigation = useCallback(() => {\n    setShowModal(false);\n    confirmNavigationRef.current?.();\n  }, []);\n\n  const handleSaveAndCloseModal = useCallback(async () => {\n    try {\n      if (manualSaveOnUnsavedChanges) manualSaveRef.current = true;\n\n      await onSave();\n      setShowModal(false);\n    } catch (err) {\n      const clientError = await getClientErrorObject(err);\n      throw new Error(\n        clientError.message ||\n          clientError.error ||\n          t('Sorry, an error occurred'),\n        { cause: err },\n      );\n    }\n  }, [manualSaveOnUnsavedChanges, onSave]);\n\n  const triggerManualSave = useCallback(() => {\n    manualSaveRef.current = true;\n    onSave();\n  }, [onSave]);\n\n  const blockCallback = useCallback(\n    (\n      {\n        pathname,\n        search,","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset-frontend/src/hooks/useUnsavedChangesPrompt/index.ts#L48-L84","documentation":"Thrown by useUnsavedChangesPrompt's handleSaveAndCloseModal when onSave rejects. getClientErrorObject pulls the message/error field out of the failed response; the original error is preserved as cause. The visible text is the backend's own message (e.g. validation failure while saving a chart/dashboard) or the fallback string.","triggerScenarios":"User clicks 'Save and close' on the unsaved-changes modal and the save request fails — chart/dashboard validation errors (40x), session expiry (401), a name collision, or backend 5xx during the PUT/POST.","commonSituations":"Saving a chart with params the API rejects; expired session after sitting on an edit page; backend restart mid-save; permission loss on the object (owner changed).","solutions":["Inspect err.cause / the network response body for the specific validation message and fix the flagged field.","Re-authenticate if status was 401, then retry the save from the modal.","If the object's ownership/permissions changed, have an Admin restore the user's rights before saving.","Keep a manual copy of edits (the modal stays open on failure) before reloading the page."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await handleSaveAndClose();\n} catch (e) {\n  const detail = (e as Error)?.cause instanceof Error ? String((e as Error).cause) : '';\n  notify({ message: (e as Error).message, description: detail });\n  // modal stays open; user can retry or copy edits\n}","preventionTips":["Keep the modal open on save failure so edits are not lost","Log e.cause to capture the backend validation message","Refresh sessions on long-lived edit pages"],"tags":["save","unsaved-changes","error-wrapping","network"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}