JuliusBrussee/caveman · error
device login timed out before approval
Error message
device login timed out before approval
What it means
Thrown by the CLI device-login (OAuth device flow) loop in `caveman login`. The loop polls the authorization server for approval and exits with this error when the retry budget is exhausted before the user approves the sign-in. It indicates the device code expired or the user never completed browser approval in time.
Solutions
- Re-run `caveman login` and approve promptly in the browser when prompted.
- Open the verification URL in the default browser immediately (copy the code before it expires).
- Check for slow SSO/VPN causing delayed approval; approve on a faster network.
- If it repeatedly times out, verify the gateway/auth server is reachable and not rate-limiting (slow_down responses inflate intervalMs).
Example fix
// before: approve hours later after code expiry caveman login # ignore prompt, approve 30 min later // after caveman login # approve in browser immediately when the code is shown
Defensive patterns
Strategy: try-catch
Try / catch
try {
await login();
} catch (e) {
if (e instanceof Error && e.message === "device login timed out before approval") {
console.error("Approval not completed in time — re-running login; approve in the browser promptly.");
return login(); // one bounded retry
}
throw e;
} Prevention
- Approve the device-flow prompt in the browser as soon as the code is displayed.
- Copy the verification URL/code immediately; don't let the device code expire.
- Run login on a network where the SSO provider is reachable and fast.
When it happens
Trigger: Running `caveman login` and never visiting the verification URL, visiting it after the device code expired, or the poll loop exceeding its max attempts while `authorization_pending` persists.
Common situations: Developer runs login, gets distracted and doesn't open the browser link; slow corporate SSO approval; stale browser session where the approve button silently fails; polling interval backoff (nextDevicePollIntervalMs) exceeding the server's device-code lifetime.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- cave_auth_required
- device credential delivery acknowledgement failed
- device login failed: server did not provide a delivery…
- device login polling failed
- AbortError
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/4d59091a59c0541c.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:9793
} else if (gateway) {
console.error(` ${mark("ok")} connected; wrap routes through ${gateway}`);
}
console.error(SYNC_DISCLOSURE);
print({ authenticated: true, baseURL, gateway_url: gateway || null, organization_id: organizationId ?? null, token_store: tokenStore });
// The funnel bridge: pull the spans the local proxy already measured into
// the dashboard, once, right now (always labeled inferred; best-effort).
await syncAfterLogin();
return;
}
const errorCode = typeof tok.error === "string" ? tok.error : "";
if (errorCode === "slow_down") {
intervalMs = nextDevicePollIntervalMs(intervalMs, errorCode);
} else if (errorCode && errorCode !== "authorization_pending") {
throw new Error(`device login failed: ${errorCode}`);
}
await sleep(Math.max(intervalMs, 200));
}
throw new Error("device login timed out before approval");
}
async function logout() {
const cfg = await config();
const externalToken = Boolean(process.env.CAVE_TOKEN);
if (cfg.token && (!cfg.logoutPendingLocalCleanup || externalToken)) {
if (cfg.projectId && cfg.gatewayKeyId) {
let response: Response;
try {
response = await fetch(`${cfg.baseURL}/api/v1/projects/${encodeURIComponent(cfg.projectId)}/keys/${encodeURIComponent(cfg.gatewayKeyId)}/revoke`, {
method: "POST",
headers: { authorization: `Bearer ${cfg.token}`, "content-type": "application/json", "x-cave-csrf": "cli" },
body: "{}",
signal: AbortSignal.timeout(5000),
});
} catch {
throw new Error("caveman: remote gateway key revocation was unavailable; credentials kept — retry `caveman logout`");
}View on GitHub (pinned to 3ee70a1026)