{"record":{"id":"a3db8d535b8db7ab","repo":"JuliusBrussee/caveman","slug":"device-authorization-failed-missing-device-code","errorCode":null,"errorMessage":"device authorization failed: missing device code","messagePattern":"device authorization failed: missing device code","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":9683,"sourceCode":"function blockCloudLoginWhileBeta(): void {\n  throw new Error(\"Caveman Cloud platform is still in beta.\");\n}\n\nasync function login(argv: string[] = []) {\n  if (!argv.some((arg) => arg === \"--instance\" || arg.startsWith(\"--instance=\"))) blockCloudLoginWhileBeta();\n  const { noBrowser, instance } = validateLoginArgs(argv);\n  const baseURL = instance ?? resolveLoginBaseUrl(argv);\n\n  const codeResp = await fetch(`${baseURL}/api/v1/auth/device/code`, {\n    method: \"POST\",\n    redirect: \"error\",\n    headers: { \"content-type\": \"application/json\" },\n    body: \"{}\",\n    signal: AbortSignal.timeout(5000),\n  });\n  if (!codeResp.ok) throw new Error(`device authorization failed: HTTP ${codeResp.status}`);\n  const code = await codeResp.json();\n  if (!code.device_code) throw new Error(\"device authorization failed: missing device code\");\n\n  const verificationURL = instance ? privateVerificationURL(code, instance) : code.verification_uri_complete ?? code.verification_uri;\n  console.error(`\\n  Authorize this device in your browser:`);\n  console.error(`    ${verificationURL}`);\n  console.error(`    code: ${code.user_code}\\n`);\n  if (typeof verificationURL === \"string\" && shouldOpenLoginBrowser(noBrowser)) openLoginBrowser(verificationURL);\n\n  let intervalMs = Math.max(0, Number(code.interval ?? 5)) * 1000;\n  const deadline = Date.now() + Number(code.expires_in ?? 600) * 1000;\n  while (Date.now() < deadline) {\n    let tok: Record<string, unknown>;\n    let tokenStatus = 0;\n    let retryAfterMs = 0;\n    try {\n      const tokResp = await fetch(`${baseURL}/api/v1/auth/device/token`, {\n        method: \"POST\",\n        redirect: \"manual\",\n        headers: { \"content-type\": \"application/json\" },","sourceCodeStart":9665,"sourceCodeEnd":9701,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L9665-L9701","documentation":"After a successful (2xx) device-authorization response, the CLI requires the JSON body to contain a device_code, which is needed to poll the token endpoint. A 2xx response without device_code means the server answered but not with a valid RFC 8628 payload, so login cannot proceed.","triggerScenarios":"The device/code endpoint returns 200 with JSON that lacks a truthy device_code field — e.g. an error envelope with HTTP 200, a proxy returning an HTML/empty page as JSON, or a non-standard authorization server response shape.","commonSituations":"Reverse proxy or captive portal intercepting the request and returning 200 with unexpected content; identity provider returning an error object instead of a device grant; pointing --instance at the wrong service that returns 200 for any path.","solutions":["Verify the --instance URL targets the actual authorization server, not a proxy or wrong service","Inspect the raw response of the device/code endpoint (curl with -i) to see what is actually returned","Confirm the identity provider supports the device authorization grant (RFC 8628)","Check for captive portals or middleware rewriting responses"],"exampleFix":"// before\nconst code = await codeResp.json();\n// after (server-side fix: return a proper device grant)\nres.json({ device_code, user_code, verification_uri, verification_uri_complete, interval });","handlingStrategy":"type-guard","validationCode":"const body = await resp.json();\nif (typeof body.device_code !== \"string\" || !body.device_code) throw new Error(\"device code endpoint returned an invalid payload\");","typeGuard":"function hasDeviceCode(c) { return c != null && typeof c.device_code === \"string\" && c.device_code.length > 0; }","tryCatchPattern":"try { await login({ instance }) } catch (e) { if (e.message.includes(\"missing device code\")) console.error(\"Instance did not return an RFC 8628 device grant; check proxy/IdP\"); }","preventionTips":["Point --instance directly at the authorization server, not through rewriting proxies","Confirm the IdP supports the device authorization grant","Log raw endpoint responses when debugging instance setup"],"tags":["oauth","device-flow","unexpected-response","validation"],"backgroundTag":"unexpected-response-shape","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"}