{"record":{"id":"fbd49cba268080d7","repo":"JuliusBrussee/caveman","slug":"device-login-polling-failed-error-instanceof-er-fbd49c","errorCode":null,"errorMessage":"device login polling failed: ${error instanceof Error ? error.message : String(error)}","messagePattern":"device login polling failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/device-auth/src/index.ts","lineNumber":134,"sourceCode":"    let retryAfterMs = 0;\n    try {\n      const response = await fetcher(`${baseURL}/api/v1/auth/device/token`, {\n        method: \"POST\",\n        headers: { \"content-type\": \"application/json\", \"x-cave-client\": options.client },\n        body: JSON.stringify({ device_code: code.device_code }),\n        signal: requestSignal(options.signal, 5000),\n      });\n      status = response.status;\n      const retryAfter = response.headers.get(\"retry-after\");\n      if (retryAfter !== null) {\n        const seconds = Number(retryAfter);\n        if (Number.isFinite(seconds) && seconds >= 0) retryAfterMs = seconds * 1000;\n      }\n      payload = await response.json() as Record<string, unknown>;\n    } catch (error) {\n      if (options.signal?.aborted) throw options.signal.reason;\n      if (Date.now() >= deadline) {\n        throw new Error(`device login polling failed: ${error instanceof Error ? error.message : String(error)}`);\n      }\n      await wait(Math.max(intervalMs, retryAfterMs, 200));\n      continue;\n    }\n    if (status === 429) {\n      await wait(Math.max(intervalMs, retryAfterMs, 200));\n      continue;\n    }\n    const accessToken = typeof payload.access_token === \"string\" ? payload.access_token : \"\";\n    if (accessToken !== \"\") {\n      const credentials = { ...payload, access_token: accessToken } as DeviceCredentials;\n      let acknowledged = false;\n      return {\n        code: structuredClone(code),\n        credentials: structuredClone(credentials),\n        async acknowledge() {\n          if (acknowledged) return;\n          await acknowledge({","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/df2ccd85c94ec3c8289cb62ac020d241ccfb0c60/packages/device-auth/src/index.ts#L116-L152","documentation":"During token polling, if the poll request itself throws (network failure, malformed body from response.json()) and the code deadline has passed, the library wraps the underlying error in this message instead of a plain timeout error. It distinguishes 'polling broke and time ran out' from orderly expiry.","triggerScenarios":"A poll to the token endpoint throws inside the try block (fetch network error, invalid JSON, aborted-connection issues) while Date.now() >= deadline — i.e. the failure happens on or after the device-code expiry.","commonSituations":"User waits until the code nearly expires, then the network drops; flaky Wi-Fi/VPN during long polls; token endpoint returning non-JSON (HTML error page) on the final poll; sleeping laptop suspending the connection until after expiry.","solutions":["Restart the device flow to get a fresh code — the original code has expired, so retrying cannot succeed.","Fix the underlying network issue shown in the inner error (VPN, DNS, proxy) before retrying.","Shorten approval time: open verification_uri promptly so polling completes before expiry.","Check why the token endpoint returned unparseable JSON (proxy interference, server error page) via `curl -i` to the token endpoint."],"exampleFix":"// before (starting the flow, then idling until the code expires)\nawait runCavemanDeviceFlow({ ... }); // user approves 20 minutes later\n// after\nawait runCavemanDeviceFlow({ ...onCode: async (code) => { await openBrowser(code.verification_uri); } }); // approve promptly, well before expires_in elapses","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await runCavemanDeviceFlow(options);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"device login polling failed\")) {\n    console.error(\"Network error while polling after code expiry — restart the device flow on a stable connection\");\n  } else throw e;\n}","preventionTips":["Approve the device code promptly; don't let it sit until expiry.","Run device flows on stable networks (avoid suspending the machine mid-flow).","Ensure the token endpoint returns JSON, not HTML error pages (check proxies).","Restart the flow after any network interruption — expired codes cannot be resumed."],"tags":["device-auth","polling","timeout","network"],"backgroundTag":"device-polling-failed","analyzedSha":"df2ccd85c94ec3c8289cb62ac020d241ccfb0c60","analyzedAt":"2026-08-31T22:10:17.934Z","contentChangedAt":"2026-08-31T22:10:17.934Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}