{"record":{"id":"d1820491e48782c3","repo":"decolua/9router","slug":"device-code-expired","errorCode":null,"errorMessage":"Device code expired","messagePattern":"Device code expired","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/github.js","lineNumber":91,"sourceCode":"      const data = await response.json();\n\n      if (data.access_token) {\n        spinner.succeed(\"GitHub authentication successful!\");\n        return {\n          access_token: data.access_token,\n          token_type: data.token_type,\n          scope: data.scope,\n        };\n      } else if (data.error === \"authorization_pending\") {\n        // Continue polling\n        continue;\n      } else if (data.error === \"slow_down\") {\n        // Increase polling interval\n        interval += 5000;\n        continue;\n      } else if (data.error === \"expired_token\") {\n        spinner.fail(\"Device code expired. Please try again.\");\n        throw new Error(\"Device code expired\");\n      } else if (data.error === \"access_denied\") {\n        spinner.fail(\"Access denied by user.\");\n        throw new Error(\"Access denied\");\n      } else {\n        spinner.fail(\"Failed to get access token.\");\n        throw new Error(data.error_description || data.error);\n      }\n    }\n  }\n\n  /**\n   * Get Copilot token using GitHub access token\n   */\n  async getCopilotToken(accessToken) {\n    const response = await fetch(`${GITHUB_CONFIG.copilotTokenUrl}`, {\n      headers: {\n        Authorization: `Bearer ${accessToken}`, // GitHub API typically uses Bearer\n        Accept: \"application/json\",","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/github.js#L73-L109","documentation":"While polling GitHub's token endpoint in the device flow, GitHub can answer `error: expired_token`, meaning the device code exceeded its lifetime (GitHub's device codes expire after ~15 minutes) because the user never completed browser authorization in time. pollAccessToken() prints 'Device code expired. Please try again.' and throws \"Device code expired\" at github.js:91.","triggerScenarios":"pollAccessToken() received `{\"error\":\"expired_token\"}` from https://github.com/login/oauth/access_token because no successful authorization happened before the device code TTL elapsed.","commonSituations":"User saw the code but got distracted or never opened the browser; the machine running the CLI is headless so the verification URL was never visited; slow_down retries stretched polling past the expiry window.","solutions":["Re-run the authentication flow and enter the new user code at https://github.com/login/device promptly.","Complete the browser authorization immediately after the code is displayed instead of leaving it pending.","If running headless, manually open the printed verification_uri on another device and enter the user code.","Avoid long pauses between starting the flow and authorizing; restart if the flow sat for more than ~15 minutes."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"function isTokenExpiredError(data) {\n  return data !== null && typeof data === 'object' && data.error === 'expired_token';\n}","tryCatchPattern":"try {\n  const tokens = await service.pollAccessToken(deviceCode, verificationUri, userCode);\n} catch (err) {\n  if (err.message === 'Device code expired') {\n    console.error('The device code timed out — restarting the flow with a fresh code...');\n    return authenticate(); // full restart issues a new device code\n  }\n  throw err;\n}","preventionTips":["Enter the user code at the verification URL promptly after it is displayed.","On headless machines, print the verification_uri and user_code prominently and open them on another device immediately.","Treat 'Device code expired' as a normal restart signal — rerun the whole flow, never reuse the old device_code.","Keep polling intervals within GitHub's guidance so slow_down adjustments don't stretch past the TTL."],"tags":["oauth","github","device-flow","timeout"],"backgroundTag":"oauth-device-code-expired","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}