{"record":{"id":"55361c0c3f5d9733","repo":"JuliusBrussee/caveman","slug":"device-login-polling-failed-error-instanceof-er","errorCode":null,"errorMessage":"device login polling failed: ${error instanceof Error ? error.message : String(error)}","messagePattern":"device login polling failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":9718,"sourceCode":"        redirect: \"manual\",\n        headers: { \"content-type\": \"application/json\" },\n        body: JSON.stringify({ device_code: code.device_code }),\n        signal: AbortSignal.timeout(5000),\n      });\n      tokenStatus = tokResp.status;\n      const retryAfter = tokResp.headers.get(\"retry-after\");\n      if (retryAfter) {\n        const seconds = Number(retryAfter);\n        if (Number.isFinite(seconds) && seconds >= 0) retryAfterMs = seconds * 1000;\n      }\n      tok = tokenStatus >= 300 && tokenStatus < 400 ? {} : await tokResp.json() as Record<string, unknown>;\n    } catch (error) {\n      // RFC 8628 polling is retryable: a dropped connection or malformed\n      // transient response must not consume the approved code or abort login\n      // before the bounded device deadline. The next poll can reclaim the\n      // server-side lease and replay the same durable bundle.\n      if (Date.now() >= deadline) {\n        throw new Error(`device login polling failed: ${error instanceof Error ? error.message : String(error)}`);\n      }\n      await sleep(Math.max(intervalMs, retryAfterMs, 200));\n      continue;\n    }\n    if (tokenStatus >= 300 && tokenStatus < 400) throw new Error(\"device login refused a redirected token endpoint\");\n    if (tokenStatus === 429) {\n      // rateLimitAuth returns a nested cave error envelope rather than the RFC\n      // `error` string. Status is the authoritative retry signal here.\n      await sleep(Math.max(intervalMs, retryAfterMs, 200));\n      continue;\n    }\n    const accessToken = typeof tok.access_token === \"string\" ? tok.access_token : \"\";\n    if (accessToken) {\n\t  if (instance && (tokenStatus < 200 || tokenStatus >= 300 || tok.credential_kind !== \"none\" ||\n\t      [\"gateway_api_key\", \"gateway_key_id\", \"gateway_url\"].some((key) => tok[key] != null) ||\n\t      typeof tok.refresh_token !== \"string\" || !tok.refresh_token || typeof tok.project_id !== \"string\" || !tok.project_id ||\n\t      typeof tok.delivery_ack_token !== \"string\" || !tok.delivery_ack_token || typeof tok.scope !== \"string\" || !tok.scope ||\n\t      tok.scope.split(/\\s+/).some((scope) => scope === \"proxy:write\" || scope === \"sdk:write\"))) {","sourceCodeStart":9700,"sourceCodeEnd":9736,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L9700-L9736","documentation":"While polling the RFC 8628 token endpoint, transient failures (dropped connections, malformed responses) are retried until a bounded device deadline. If an exception occurs and the deadline has already passed, the CLI gives up and throws this error wrapping the underlying message. Earlier errors while time remains are silently retried.","triggerScenarios":"An exception is thrown during a token poll AND Date.now() >= deadline — i.e. the last poll before the device-code grant expired failed due to a network error, JSON parse failure, or similar transient fault.","commonSituations":"User never opened the verification URL, so polling ran the full window; flaky network or VPN dropping late in the polling window; instance becoming unreachable mid-flow; very short device-code lifetime on the server.","solutions":["Re-run login and complete browser approval promptly (the code expires)","Check network connectivity/VPN stability during the polling window","Increase the server-side device-code lifetime/interval if you control the instance","Retry login once the instance is reachable again"],"exampleFix":"// before\nawait sleep(intervalMs); // polling continues past deadline on network failure\n// after\nif (Date.now() >= deadline) throw new Error(`device login polling failed: ${err.message}`);\nawait sleep(Math.max(intervalMs, retryAfterMs, 200));","handlingStrategy":"retry","validationCode":"const reachable = await fetch(instance, { signal: AbortSignal.timeout(3000) }).then(r => r.ok).catch(() => false);\nif (!reachable) console.warn(\"Instance unreachable; polling may fail before approval\");","typeGuard":null,"tryCatchPattern":"try { await login({ instance }) } catch (e) { if (e.message.startsWith(\"device login polling failed:\")) await retryLoginWithBackoff(); }","preventionTips":["Complete browser approval promptly after starting login","Use a stable network/VPN during login","Ensure the device-code lifetime exceeds the expected approval time"],"tags":["network","timeout","oauth","device-flow"],"backgroundTag":"request-timeout","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}