JuliusBrussee/caveman · error
private device authorization returned an invalid code…
Error message
private device authorization returned an invalid code response
What it means
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.
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.
Example fix
// before (server response)
{"device_code":"abc","user_code":"abcd-1234","expires_in":"600"}
// after
{"device_code":"<opaque>","user_code":"ABCD-EFGH","expires_in":600,"interval":5} Defensive patterns
Strategy: validation
Validate before calling
const USER_CODE_RE = /^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/;
function isValidDeviceCodeResponse(c) {
return typeof c.device_code === "string" && c.device_code.length > 0 && c.device_code.length <= 4096 &&
typeof c.user_code === "string" && USER_CODE_RE.test(c.user_code) &&
typeof c.expires_in === "number" && Number.isFinite(c.expires_in) && c.expires_in > 0 && c.expires_in <= 3600 &&
(c.interval === undefined || (typeof c.interval === "number" && Number.isFinite(c.interval) && c.interval >= 0 && c.interval <= 60));
} Try / catch
try {
await caveman.login({ instance });
} catch (e) {
if (e instanceof Error && e.message === "private device authorization returned an invalid code response") {
console.error("The instance's device authorization response is non-conformant; check the authorization server's response shape (RFC 8628).", e.message);
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- private device authorization omitted its browser URL
- device authorization failed: HTTP
- device authorization failed: missing device code
- device credential delivery acknowledgement failed
- device login failed
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/3c86e12dcc784c30.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:9602
if (values.has("--base-url") || values.has("--gateway-url")) commandUsage(usage);
const url = new URL(instance);
if (!secureLoginURL(url) || url.pathname !== "/" || url.search || url.hash || url.hostname.replace(/\.$/, "") === new URL(PROD_API_URL).hostname) {
throw new Error("--instance requires a private HTTPS origin (HTTP loopback is allowed for local development)");
}
return { noBrowser, instance: url.origin };
}
function secureLoginURL(url: URL, allowLoopback = true): boolean {
return !url.username && !url.password && (url.protocol === "https:" ||
(allowLoopback && url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)));
}
function privateVerificationURL(code: Record<string, unknown>, instance: string): string {
if (typeof code.device_code !== "string" || !code.device_code || code.device_code.length > 4096 ||
typeof code.user_code !== "string" || !/^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/.test(code.user_code) ||
typeof code.expires_in !== "number" || !Number.isFinite(code.expires_in) || code.expires_in <= 0 || code.expires_in > 3600 ||
(code.interval !== undefined && (typeof code.interval !== "number" || !Number.isFinite(code.interval) || code.interval < 0 || code.interval > 60))) {
throw new Error("private device authorization returned an invalid code response");
}
const value = code.verification_uri_complete ?? code.verification_uri;
if (typeof value !== "string") throw new Error("private device authorization omitted its browser URL");
const url = new URL(value);
if (!secureLoginURL(url, new URL(instance).protocol === "http:") || url.hash) {
throw new Error("private device authorization returned an unsafe browser URL");
}
url.searchParams.set("user_code", code.user_code);
url.searchParams.set("connection", "mcp");
url.searchParams.set("client_name", "Caveman CLI");
return url.href;
}
function openLoginBrowser(url: string): void {
const opener = loginBrowserOpener(url);
if (!which(opener.command)) {
process.stderr.write(` browser opener unavailable; open ${url}\n`);
return;View on GitHub (pinned to 3ee70a1026)