{"record":{"id":"48b1e8d4252fefef","repo":"JuliusBrussee/caveman","slug":"device-login-failed-errorcode-48b1e8","errorCode":null,"errorMessage":"device login failed: ${errorCode}","messagePattern":"device login failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/device-auth/src/index.ts","lineNumber":168,"sourceCode":"        async acknowledge() {\n          if (acknowledged) return;\n          await acknowledge({\n            baseURL,\n            client: options.client,\n            code,\n            credentials,\n            fetcher,\n            ...(options.signal === undefined ? {} : { signal: options.signal }),\n            sleep: wait,\n          });\n          acknowledged = true;\n        },\n      };\n    }\n    const errorCode = typeof payload.error === \"string\" ? payload.error : \"\";\n    if (errorCode === \"slow_down\") intervalMs = nextDevicePollIntervalMs(intervalMs, errorCode);\n    else if (errorCode !== \"\" && errorCode !== \"authorization_pending\") {\n      throw new Error(`device login failed: ${errorCode}`);\n    }\n    await wait(Math.max(intervalMs, 200));\n  }\n  throw new Error(\"device login timed out before approval\");\n}\n","sourceCodeStart":150,"sourceCodeEnd":174,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/df2ccd85c94ec3c8289cb62ac020d241ccfb0c60/packages/device-auth/src/index.ts#L150-L174","documentation":"While polling the token endpoint, the OAuth device grant can return an error field. authorization_pending and slow_down are expected and handled; any other error code (access_denied, expired_token, invalid_client, etc.) is fatal and thrown with the raw code embedded.","triggerScenarios":"The token endpoint responds with a JSON payload whose error is a string other than \"authorization_pending\" or \"slow_down\" — most commonly access_denied (user rejected) or expired_token.","commonSituations":"User clicks 'Cancel'/'Deny' on the verification page (access_denied); the device code expired server-side between polls (expired_token); client credentials rejected (invalid_client) after a client-id change; account/tenant restrictions blocking the grant.","solutions":["Read the embedded code: access_denied means the user declined — restart the flow and approve the request.","If expired_token, restart the device flow to get a fresh device code.","Check the client identifier sent to /auth/device/code matches a registered client (invalid_client).","Verify the account/tenant permits device authorization (org policy may deny it, surfacing as access_denied).","If the code is unexpected, compare it against the OAuth device-grant error registry to find the server-side cause."],"exampleFix":"// before (user denied, then blindly retrying the same flow)\ntry { await runCavemanDeviceFlow(opts); } catch { await runCavemanDeviceFlow(opts); }\n// after\ntry { await runCavemanDeviceFlow(opts); } catch (e) {\n  if (String(e).includes(\"access_denied\")) prompt(\"You must approve the login to continue\");\n  await runCavemanDeviceFlow(opts); // restart with fresh code after explaining\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await runCavemanDeviceFlow(options);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"device login failed: \")) {\n    const code = e.message.slice(\"device login failed: \".length);\n    if (code === \"access_denied\") console.error(\"Login was denied — approve the request and retry\");\n    else if (code === \"expired_token\") console.error(\"Code expired — restarting flow\");\n    else throw e;\n  } else throw e;\n}","preventionTips":["Instruct users to click Approve, not Cancel, on the verification page.","Complete the flow before the device code expires (expires_in).","Keep the client identifier registered/valid with the auth server.","Handle access_denied and expired_token explicitly in UX instead of surfacing raw errors."],"tags":["device-auth","oauth","access-denied","polling"],"backgroundTag":"oauth-device-flow-denied","analyzedSha":"df2ccd85c94ec3c8289cb62ac020d241ccfb0c60","analyzedAt":"2026-08-31T22:10:17.934Z","contentChangedAt":"2026-08-31T22:10:17.934Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}