{"record":{"id":"3c86e12dcc784c30","repo":"JuliusBrussee/caveman","slug":"private-device-authorization-returned-an-invalid-code","errorCode":null,"errorMessage":"private device authorization returned an invalid code response","messagePattern":"private device authorization returned an invalid code response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":9602,"sourceCode":"  if (values.has(\"--base-url\") || values.has(\"--gateway-url\")) commandUsage(usage);\n  const url = new URL(instance);\n  if (!secureLoginURL(url) || url.pathname !== \"/\" || url.search || url.hash || url.hostname.replace(/\\.$/, \"\") === new URL(PROD_API_URL).hostname) {\n    throw new Error(\"--instance requires a private HTTPS origin (HTTP loopback is allowed for local development)\");\n  }\n  return { noBrowser, instance: url.origin };\n}\n\nfunction secureLoginURL(url: URL, allowLoopback = true): boolean {\n  return !url.username && !url.password && (url.protocol === \"https:\" ||\n    (allowLoopback && url.protocol === \"http:\" && [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname)));\n}\n\nfunction privateVerificationURL(code: Record<string, unknown>, instance: string): string {\n  if (typeof code.device_code !== \"string\" || !code.device_code || code.device_code.length > 4096 ||\n      typeof code.user_code !== \"string\" || !/^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/.test(code.user_code) ||\n      typeof code.expires_in !== \"number\" || !Number.isFinite(code.expires_in) || code.expires_in <= 0 || code.expires_in > 3600 ||\n      (code.interval !== undefined && (typeof code.interval !== \"number\" || !Number.isFinite(code.interval) || code.interval < 0 || code.interval > 60))) {\n    throw new Error(\"private device authorization returned an invalid code response\");\n  }\n  const value = code.verification_uri_complete ?? code.verification_uri;\n  if (typeof value !== \"string\") throw new Error(\"private device authorization omitted its browser URL\");\n  const url = new URL(value);\n  if (!secureLoginURL(url, new URL(instance).protocol === \"http:\") || url.hash) {\n    throw new Error(\"private device authorization returned an unsafe browser URL\");\n  }\n  url.searchParams.set(\"user_code\", code.user_code);\n  url.searchParams.set(\"connection\", \"mcp\");\n  url.searchParams.set(\"client_name\", \"Caveman CLI\");\n  return url.href;\n}\n\nfunction openLoginBrowser(url: string): void {\n  const opener = loginBrowserOpener(url);\n  if (!which(opener.command)) {\n    process.stderr.write(`  browser opener unavailable; open ${url}\\n`);\n    return;","sourceCodeStart":9584,"sourceCodeEnd":9620,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L9584-L9620","documentation":"During private-instance device authorization, Caveman validates the OAuth device-code response before using it: device_code must be a non-empty string of at most 4096 chars, user_code must match the XXXX-XXXX charset pattern ([A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}), expires_in must be a finite number in (0, 3600], and the optional interval must be a finite number in [0, 60]. If the private instance's authorization server returns anything else, the CLI throws this error rather than polling or displaying a malformed code.","triggerScenarios":"Calling the login flow against a private instance whose device authorization endpoint returns a non-conformant payload — e.g. expires_in as a string, expires_in > 3600 or <= 0, a user_code like \"abcd-1234\" (lowercase/ambiguous chars), a missing or empty device_code, or an out-of-range interval.","commonSituations":"Running a self-hosted/proxied authorization server (or a mock) that doesn't follow the expected device-flow response shape; a gateway rewriting the JSON response; pointing the CLI at an endpoint from a different OAuth provider version with different field types.","solutions":["Fix the private instance's device authorization endpoint to return RFC 8628-shaped fields: device_code (string ≤ 4096), user_code matching [A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}, numeric expires_in in (0, 3600], optional numeric interval in [0, 60].","Check for a proxy/gateway or middleware mangling the JSON (renaming fields, stringifying numbers) and bypass or fix it.","Verify you are talking to the correct Caveman-compatible authorization endpoint for the instance origin passed via --instance."],"exampleFix":"// before (server response)\n{\"device_code\":\"abc\",\"user_code\":\"abcd-1234\",\"expires_in\":\"600\"}\n// after\n{\"device_code\":\"<opaque>\",\"user_code\":\"ABCD-EFGH\",\"expires_in\":600,\"interval\":5}","handlingStrategy":"validation","validationCode":"const USER_CODE_RE = /^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/;\nfunction isValidDeviceCodeResponse(c) {\n  return typeof c.device_code === \"string\" && c.device_code.length > 0 && c.device_code.length <= 4096 &&\n    typeof c.user_code === \"string\" && USER_CODE_RE.test(c.user_code) &&\n    typeof c.expires_in === \"number\" && Number.isFinite(c.expires_in) && c.expires_in > 0 && c.expires_in <= 3600 &&\n    (c.interval === undefined || (typeof c.interval === \"number\" && Number.isFinite(c.interval) && c.interval >= 0 && c.interval <= 60));\n}","typeGuard":null,"tryCatchPattern":"try {\n  await caveman.login({ instance });\n} catch (e) {\n  if (e instanceof Error && e.message === \"private device authorization returned an invalid code response\") {\n    console.error(\"The instance's device authorization response is non-conformant; check the authorization server's response shape (RFC 8628).\", e.message);\n  } else throw e;\n}","preventionTips":["Test your private instance's device-flow endpoint against RFC 8628 examples before pointing the CLI at it.","Ensure proxies/gateways don't retype numeric fields (e.g. expires_in) to strings.","Use a user_code alphabet excluding ambiguous characters (I, L, O, 0, 1) in the XXXX-XXXX format."],"tags":["oauth","device-flow","response-validation","private-instance","login"],"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"}