{"record":{"id":"0679245971e41e0f","repo":"decolua/9router","slug":"data-errordescription-data-error","errorCode":null,"errorMessage":"data.errorDescription || data.error","messagePattern":"data\\.errorDescription \\|\\| data\\.error","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/shared/components/OAuthModal.js","lineNumber":163,"sourceCode":"      try {\n        const res = await fetch(`/api/oauth/${provider}/poll`, {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify({ deviceCode, codeVerifier, extraData }),\n        });\n\n        const data = await res.json();\n\n        if (data.success) {\n          pollingAbortRef.current = true; // Stop polling immediately\n          setStep(\"success\");\n          setPolling(false);\n          onSuccess?.();\n          return;\n        }\n\n        if (data.error === \"expired_token\" || data.error === \"access_denied\") {\n          throw new Error(data.errorDescription || data.error);\n        }\n\n        if (data.error === \"slow_down\") {\n          interval = Math.min(interval + 5, 30);\n        }\n      } catch (err) {\n        setError(err.message);\n        setStep(\"error\");\n        setPolling(false);\n        return;\n      }\n    }\n\n    setError(\"Authorization timeout\");\n    setStep(\"error\");\n    setPolling(false);\n  }, [provider, onSuccess]);\n","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/shared/components/OAuthModal.js#L145-L181","documentation":"During the device-code polling loop in OAuthModal, when the poll response carries error === \"expired_token\" or \"access_denied\", the modal throws an Error preferring the provider-supplied `errorDescription`, falling back to the bare error code. This ends polling with the error step. `slow_down` and `authorization_pending` are handled separately (backoff / keep polling) and do not reach this throw.","triggerScenarios":"The device-flow poll endpoint returns { error: \"expired_token\" } — the device code outlived its validity because the user took too long to authorize — or { error: \"access_denied\" } — the user explicitly denied the consent request at the provider.","commonSituations":"User leaves the device-authorization page open but doesn't approve before the code expires (typically 5–15 min); user clicks 'Deny'/'Cancel' on the consent screen; interval backoff mishandled causing provider-side expiry; workplace SSO policy auto-denies the request.","solutions":["Restart the device flow to get a new device/user code and approve it promptly.","If access_denied, re-run the flow and approve the consent screen (check with your admin if SSO policy blocks it).","Read the errorDescription in the thrown message — it states whether the code expired or consent was denied.","For repeated expired_token, reduce friction: have the auth page open and ready before starting, and don't pause/hibernate the machine mid-flow."],"exampleFix":"// before\nif (data.error === \"expired_token\" || data.error === \"access_denied\") {\n  throw new Error(data.errorDescription || data.error);\n}\n// after\nif (data.error === \"expired_token\" || data.error === \"access_denied\") {\n  const reason = data.error === \"expired_token\"\n    ? \"Device code expired — restart the sign-in and approve within the time limit\"\n    : \"Authorization was denied — restart the sign-in and approve the consent request\";\n  throw new Error(data.errorDescription || reason);\n}","handlingStrategy":"retry","validationCode":"// only treat terminal errors as fatal; keep polling pending/slow_down\nconst TERMINAL = [\"expired_token\", \"access_denied\"];\nif (TERMINAL.includes(data.error)) {\n  // stop polling and prompt a fresh device-code request\n  setPolling(false);\n  setError(data.errorDescription || data.error);\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await pollDeviceToken();\n} catch (err) {\n  if (err.message.includes(\"expired\")) {\n    // auto-restart the device flow once\n    await startDeviceFlow();\n  } else {\n    setError(err.message);\n  }\n}","preventionTips":["Approve the device-code page promptly; codes expire in minutes.","Implement automatic restart of the device flow on expired_token.","Respect slow_down backoff so the provider doesn't kill the poll window.","Show a visible countdown so users know the approval deadline."],"tags":["oauth","device-flow","expired-token","access-denied"],"backgroundTag":"device-code-expired","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}