{"record":{"id":"22005360e9701f28","repo":"coleam00/Archon","slug":"result-code","errorCode":null,"errorMessage":"${result.code}","messagePattern":"\\$\\{result\\.code\\}","errorType":"error_code","errorClass":"DeviceFlowError","httpStatus":null,"severity":"error","filePath":"packages/core/src/github-auth/device-flow.ts","lineNumber":129,"sourceCode":"  opts: PollOptions = {}\n): Promise<DeviceAccessToken> {\n  const sleep = opts.sleep ?? defaultSleep;\n  let interval = Math.max(1, intervalSeconds);\n  for (;;) {\n    if (opts.signal?.aborted) {\n      throw new DeviceFlowError('aborted', 'Device flow polling was aborted');\n    }\n    await sleep(interval * 1000);\n    const result = await pollDeviceFlowOnce(clientId, deviceCode);\n    if (result.status === 'authorized') return result.token;\n    if (result.status === 'pending') continue;\n    if (result.status === 'slow_down') {\n      // Honor the server's new interval, else keep the current. Floor at 1s so a\n      // malformed `interval: 0` can't turn this into a busy loop.\n      interval = Math.max(1, result.interval);\n      continue;\n    }\n    throw new DeviceFlowError(result.code);\n  }\n}\n\n/** Result of a single (non-blocking) device-flow poll. */\nexport type PollOnceResult =\n  | { status: 'pending' }\n  | { status: 'slow_down'; interval: number }\n  | { status: 'authorized'; token: DeviceAccessToken }\n  | { status: 'error'; code: string };\n\n/**\n * One non-blocking poll attempt. Used by the web endpoint (which polls from the\n * browser) and internally by {@link pollDeviceFlow}'s blocking loop.\n */\nexport async function pollDeviceFlowOnce(\n  clientId: string,\n  deviceCode: string\n): Promise<PollOnceResult> {","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/github-auth/device-flow.ts#L111-L147","documentation":"When a single poll returns a terminal status other than 'pending' or 'slow_down' (e.g. authorization_pending expired, access_denied, bad_verification_code), pollDeviceFlow throws DeviceFlowError with GitHub's error code as the message.","triggerScenarios":"pollDeviceFlow's pollDeviceFlowOnce returns a status like 'denied' or 'expired' — the user clicked 'I do not authorize', the user code expired, or the wrong code was entered.","commonSituations":"User takes too long and the device code expires; user denies the authorization request; user types the wrong code; polling continues after the token was already consumed.","solutions":["Restart startDeviceFlow to get a fresh device/user code and try again","If code is access_denied, ask the user to approve the request next time","If code is authorization_pending/expired, complete the login faster or raise the code lifetime","Match on the specific code and surface a user-friendly message"],"exampleFix":"// before\nconst token = await pollDeviceFlow(clientId, data.device_code, 5);\n// after\ntry {\n  const token = await pollDeviceFlow(clientId, data.device_code, 5);\n} catch (e) {\n  if (e instanceof DeviceFlowError && e.code === 'authorization_pending') {\n    return startDeviceFlow(clientId); // fresh code\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { return await pollDeviceFlow(clientId, deviceCode, interval); } catch (e) { if (e instanceof DeviceFlowError && e.code === 'authorization_pending') return restartDeviceFlow(); throw e; }","preventionTips":["Show the user code with a visible countdown of its expiry to encourage quick entry","Handle access_denied and expired codes by restarting startDeviceFlow","Do not reuse a device_code after a terminal error"],"tags":["oauth","device-flow","authorization","github"],"backgroundTag":"oauth-device-flow-error","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}