{"record":{"id":"77c05e7d34add5a8","repo":"chatboxai/chatbox","slug":"device-flow-timed-out","errorCode":null,"errorMessage":"Device flow timed out","messagePattern":"Device flow timed out","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/oauth/providers/github-copilot.ts","lineNumber":128,"sourceCode":"          // Use the GitHub access token directly as the Copilot API credential\n          // (same approach as openllmprovider)\n          return {\n            accessToken: raw.access_token,\n            // No refresh token — the GitHub access token doesn't expire\n            // but the Copilot API session may need re-auth periodically\n          }\n        }\n\n        if (raw.error === 'authorization_pending') continue\n        if (raw.error === 'slow_down') {\n          intervalMs += 5000\n          continue\n        }\n\n        throw new Error(`Device flow failed: ${raw.error}`)\n      }\n\n      throw new Error('Device flow timed out')\n    } finally {\n      pendingDeviceCode = null\n    }\n  },\n\n  async refreshToken(credentials) {\n    // GitHub access tokens from device flow don't expire in the traditional sense.\n    // Just return the existing credentials.\n    return credentials\n  },\n}\n","sourceCodeStart":110,"sourceCodeEnd":140,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/oauth/providers/github-copilot.ts#L110-L140","documentation":"Thrown when the device-flow polling loop in waitForToken() reaches its 10-minute hard deadline (Date.now() + 10*60*1000) without ever receiving an access_token and without GitHub returning a terminal error. It is a guard against the user simply never completing authorization. The finally block clears pendingDeviceCode, so the flow must be restarted from scratch.","triggerScenarios":"User opened the verification URL but never entered the code or never approved; user is on a slow connection and the approval round-trip exceeded 10 minutes; the device_code lifetime is longer than this deadline and the user authorized just after timeout.","commonSituations":"Backgrounded app where the user forgot to finish; user got distracted mid-login; clock differences making the deadline trigger early.","solutions":["Catch this specific timeout and restart via startDeviceFlow() to issue a fresh device_code and user_code.","Surface a clear UI message ('Login timed out — click to try again') rather than a generic error.","Consider bumping the deadline if GitHub's documented device-code lifetime exceeds 10 minutes."],"exampleFix":"// before\nconst creds = await provider.waitForToken(signal)\n\n// after\ntry {\n  const creds = await provider.waitForToken(signal)\n  return creds\n} catch (e) {\n  if (/timed out/i.test(String(e))) {\n    const restarted = await provider.startDeviceFlow()\n    throw new ReauthRequiredError(restarted)\n  }\n  throw e\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isDeviceFlowTimeout(e: unknown): boolean {\n  return e instanceof Error && /Device flow timed out/i.test(e.message)\n}","tryCatchPattern":"try {\n  return await provider.waitForToken(signal)\n} catch (e) {\n  if (isDeviceFlowTimeout(e)) {\n  // restart with a fresh device code + user code\n  const fresh = await provider.startDeviceFlow()\n  throw new ReauthRequiredError(fresh)\n  }\n  throw e\n}","preventionTips":["Surface an in-app countdown so the user knows authorization is time-limited.","Restart startDeviceFlow() automatically (with user consent) when this timeout fires.","If timeouts are chronic, verify the deadline (10 min) vs GitHub's device-code lifetime."],"tags":["oauth","timeout","github","device-flow"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}