JuliusBrussee/caveman · error

device authorization failed: HTTP

Error message

device authorization failed: HTTP ${codeResp.status}

What it means

The CLI starts the RFC 8628 device-authorization flow by POSTing to the instance's device/code endpoint. If the HTTP response is not ok, it cannot obtain a device code and throws with the status code. This is a fail-fast check before any polling begins.

Solutions

  1. Check the printed status code: 404 usually means wrong instance/base URL, 401 a client misconfiguration, 429 too many attempts
  2. Verify the --instance URL points at the correct authorization server with the device-code endpoint enabled
  3. Wait and retry if the status is 429 or 5xx
  4. Inspect instance/server logs for the failing request

Example fix

// before
await cli.login({ instance: "https://wrong-host.example.com" });
// after
await cli.login({ instance: "https://auth.correct-instance.example.com" });
Defensive patterns

Strategy: retry

Validate before calling

const probe = await fetch(new URL("/oauth/device/code", instance), { method: "HEAD" }).catch(() => null);
if (!probe || !probe.ok && probe.status !== 405) console.warn("Device-code endpoint not reachable at instance");

Try / catch

try { await login({ instance }) } catch (e) { const m = e.message.match(/HTTP (\d+)/); if (m && (m[1] === "429" || m[1].startsWith("5"))) await backoffThenRetry(); }

Prevention

When it happens

Trigger: The POST to the device authorization endpoint returns a non-2xx status (401 wrong client/audience, 404 wrong path, 429 rate limit, 5xx server error) within the 5s AbortSignal timeout window.

Common situations: Wrong --instance URL pointing at a host without the device-code endpoint; identity provider misconfigured (missing device grant); server outage; rate limiting from repeated login attempts.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/da383df566ebaab2. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:9681

// from the returned token, never from any local input.
// Hosted login remains gated; explicit private instances use project access.
function blockCloudLoginWhileBeta(): void {
  throw new Error("Caveman Cloud platform is still in beta.");
}

async function login(argv: string[] = []) {
  if (!argv.some((arg) => arg === "--instance" || arg.startsWith("--instance="))) blockCloudLoginWhileBeta();
  const { noBrowser, instance } = validateLoginArgs(argv);
  const baseURL = instance ?? resolveLoginBaseUrl(argv);

  const codeResp = await fetch(`${baseURL}/api/v1/auth/device/code`, {
    method: "POST",
    redirect: "error",
    headers: { "content-type": "application/json" },
    body: "{}",
    signal: AbortSignal.timeout(5000),
  });
  if (!codeResp.ok) throw new Error(`device authorization failed: HTTP ${codeResp.status}`);
  const code = await codeResp.json();
  if (!code.device_code) throw new Error("device authorization failed: missing device code");

  const verificationURL = instance ? privateVerificationURL(code, instance) : code.verification_uri_complete ?? code.verification_uri;
  console.error(`\n  Authorize this device in your browser:`);
  console.error(`    ${verificationURL}`);
  console.error(`    code: ${code.user_code}\n`);
  if (typeof verificationURL === "string" && shouldOpenLoginBrowser(noBrowser)) openLoginBrowser(verificationURL);

  let intervalMs = Math.max(0, Number(code.interval ?? 5)) * 1000;
  const deadline = Date.now() + Number(code.expires_in ?? 600) * 1000;
  while (Date.now() < deadline) {
    let tok: Record<string, unknown>;
    let tokenStatus = 0;
    let retryAfterMs = 0;
    try {
      const tokResp = await fetch(`${baseURL}/api/v1/auth/device/token`, {
        method: "POST",

View on GitHub (pinned to 3ee70a1026)