JuliusBrussee/caveman · error · Error
device credential delivery acknowledgement failed (${lastErr
Error message
device credential delivery acknowledgement failed (${lastError}); credentials were persisted locally but the server may revoke them after the delivery window What it means
acknowledge() retries POST /api/v1/auth/device/ack up to 5 times with exponential backoff (only retrying on 5xx/429 or thrown errors). If every attempt fails, it throws with the last error embedded, warning that credentials were saved locally but the server may revoke them after the delivery window.
Source
Thrown at packages/device-auth/src/index.ts:82
headers: {
authorization: `Bearer ${options.credentials.access_token}`,
"content-type": "application/json",
"x-cave-client": options.client,
},
body: JSON.stringify({ device_code: options.code.device_code, ack_token: ackToken }),
signal: requestSignal(options.signal, 5000),
});
if (response.ok) return;
const body = await response.json().catch(() => null) as { error?: { code?: unknown } } | null;
const code = typeof body?.error?.code === "string" ? body.error.code : `HTTP ${response.status}`;
lastError = code;
if (response.status < 500 && response.status !== 429) break;
} catch (error) {
lastError = error instanceof Error ? error.message : String(error);
}
if (attempt < 4) await options.sleep(Math.min(2000, 200 * 2 ** attempt));
}
throw new Error(`device credential delivery acknowledgement failed (${lastError}); credentials were persisted locally but the server may revoke them after the delivery window`);
}
export async function runCavemanDeviceFlow(options: {
baseURL: string;
client: string;
fetch?: typeof globalThis.fetch;
signal?: AbortSignal;
sleep?: (ms: number) => Promise<void>;
onCode?: (code: DeviceCode) => void | Promise<void>;
}): Promise<DeviceGrant> {
const fetcher = options.fetch ?? globalThis.fetch;
const wait = options.sleep ?? defaultSleep;
const baseURL = options.baseURL.replace(/\/$/, "");
const codeResponse = await fetcher(`${baseURL}/api/v1/auth/device/code`, {
method: "POST",
headers: { "content-type": "application/json", "x-cave-client": options.client },
body: "{}",
signal: requestSignal(options.signal, 5000),View on GitHub (pinned to df2ccd85c9)
Solutions
- Confirm the ack endpoint is reachable: `curl -i ${baseURL}/api/v1/auth/device/ack` from the same environment.
- Re-run the device login once the network/server recovers — the error is transient by design; a successful re-login re-acks.
- Verify baseURL points at the correct environment (staging vs production) and isn't blocked by a proxy (check HTTPS_PROXY/NO_PROXY).
- Check server-side rate limits if lastError references 429 and back off before retrying.
- If credentials were already revoked server-side, redo the device flow to obtain fresh ones.
Example fix
// before (wrong baseURL, unreachable ack endpoint)
await acknowledge({ baseURL: "https://internal-staging.invalid", ... });
// after
await acknowledge({ baseURL: "https://api.example.com", ... }); Defensive patterns
Strategy: retry
Validate before calling
// pre-check reachability of the ack endpoint
const ping = await fetch(`${baseURL}/api/v1/auth/device/ack`, { method: "HEAD" }).catch(() => null);
if (!ping) throw new Error("ack endpoint unreachable; fix network/baseURL before login"); Try / catch
try {
await acknowledge(options);
} catch (e) {
if (e instanceof Error && e.message.includes("device credential delivery acknowledgement failed")) {
// transient: wait and redo the device login to re-ack fresh credentials
await sleep(5000);
await redoDeviceFlow();
} else throw e;
} Prevention
- Ensure the auth host is reachable (check proxies, firewalls, VPN) before device login.
- Retry the whole flow after outages — ack is retried 5 times internally, but sustained failure needs a new login.
- Check rate limits if you see repeated 429s and slow your polling cadence.
- Verify baseURL points at the intended environment.
When it happens
Trigger: All 5 ack attempts against ${baseURL}/api/v1/auth/device/ack fail — network outages, sustained 5xx server errors, or repeated 429 rate-limiting across the full retry window.
Common situations: Corporate proxy/firewall blocking the ack endpoint; auth server outage or deploy in progress; client aggressively rate-limited (429) on every retry; baseURL misconfigured to a wrong host so every request errors.
Related errors
- device authorization failed: HTTP ${codeResponse.status}
- device login polling failed: ${error instanceof Error ? erro
- cave_sandbox_network_egress_unbounded
- tool search failed with HTTP ${response.status}
- binary download failed: ${error.message}
AI-assisted analysis of JuliusBrussee/caveman@df2ccd85c9 (2026-08-31).
Data as JSON: /api/errors/2ab389acfc4b7ae8.
Report an issue: GitHub.