{"record":{"id":"6fe5fc317f172431","repo":"chatboxai/chatbox","slug":"device-flow-failed-raw-error","errorCode":null,"errorMessage":"Device flow failed: ${raw.error}","messagePattern":"Device flow failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/oauth/providers/github-copilot.ts","lineNumber":125,"sourceCode":"\n        if (raw.access_token) {\n          log.info('[OAuth:Copilot] GitHub access token obtained')\n          // 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":107,"sourceCodeEnd":140,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/oauth/providers/github-copilot.ts#L107-L140","documentation":"Thrown inside the Copilot device-flow polling loop when GitHub's access_token endpoint returns HTTP 200 with an error field that is neither 'authorization_pending' nor 'slow_down'. These are application-level OAuth errors delivered in a 200 response, so fetchJson does not catch them; the loop's own error switch does.","triggerScenarios":"error='expired_token' (user didn't authorize within the device-code lifetime), 'access_denied' (user explicitly denied the request at github.com/login/device), 'incorrect_client_credentials', 'unsupported_grant_type', or 'device_flow_disabled' for the client.","commonSituations":"User walked away past the ~15-minute device-code expiry; user clicked 'Cancel' on GitHub's device confirmation page; the OAuth App has device flow disabled in its settings; CLIENT_ID mismatch produces incorrect_client_credentials.","solutions":["Inspect raw.error: 'expired_token' and 'access_denied' should restart the flow (startDeviceFlow), not retry the same device_code.","For 'incorrect_client_credentials' / 'unsupported_grant_type' / 'device_flow_disabled', surface a config error — no amount of retrying will help.","Surface a user-facing message: 'Authorization expired, please try again' for expired_token, 'Access was denied' for access_denied."],"exampleFix":"// before\nthrow new Error(`Device flow failed: ${raw.error}`)\n\n// after\nif (raw.error === 'expired_token' || raw.error === 'access_denied') {\n  throw new Error(raw.error === 'expired_token' ? 'Device code expired. Restart login.' : 'User denied the login request.')\n}\nthrow new Error(`Device flow failed: ${raw.error}`)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"type DeviceFlowError = 'expired_token' | 'access_denied' | 'incorrect_client_credentials' | 'unsupported_grant_type' | 'device_flow_disabled'\nfunction isTerminalDeviceError(error: unknown): error is DeviceFlowError {\n  return typeof error === 'string' && ['expired_token','access_denied','incorrect_client_credentials','unsupported_grant_type','device_flow_disabled'].includes(error as string)\n}","tryCatchPattern":"try {\n  return await provider.waitForToken(signal)\n} catch (e) {\n  const msg = String(e)\n  if (/expired_token/i.test(msg) || /access_denied/i.test(msg)) {\n  // restartable: user-driven causes\n  const fresh = await provider.startDeviceFlow()\n  throw new ReauthRequiredError(fresh)\n  }\n  if (/incorrect_client|unsupported_grant|device_flow_disabled/i.test(msg)) {\n  throw new Error('GitHub device flow misconfigured for this OAuth App.')\n  }\n  throw e\n}","preventionTips":["Make sure the user authorizes within the device-code lifetime (typically ~15 min).","Confirm the OAuth App has device flow enabled in its GitHub settings.","Restart the flow on expired_token / access_denied rather than retrying the same device_code."],"tags":["oauth","github","device-flow","application-error"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}