JuliusBrussee/caveman · error
device credential delivery acknowledgement failed
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
After a successful private device login the CLI must acknowledge to the control plane that it stored the credential bundle, using a delivery_ack_token. This acknowledgement POST is retried up to 5 times (only retrying on 5xx/429 and network errors); if the last attempt still fails, this error is thrown. Credentials are already saved locally, but the server may revoke them once the delivery window expires.
Solutions
- Retry `login` once the instance is reachable — credentials are already stored locally
- Check instance health/logs for errors on the delivery-acknowledgement endpoint
- Verify network/proxy allows POSTs to the instance's acknowledgement URL
- Re-run device login to obtain a fresh grant before the delivery window revokes the credentials
Example fix
// before
await acknowledgeDeviceGrant(baseURL, credentials.access_token, code.device_code, ackToken);
// after
try {
await acknowledgeDeviceGrant(baseURL, credentials.access_token, code.device_code, ackToken);
} catch (e) {
console.warn("ack failed, re-login to refresh the grant:", e.message);
} Defensive patterns
Strategy: retry
Validate before calling
const reachable = await fetch(baseURL + "/healthz", { signal: AbortSignal.timeout(3000) }).then(r => r.ok).catch(() => false);
if (!reachable) console.warn("Instance unreachable; acknowledgement will likely fail"); Try / catch
try { await login({ instance }) } catch (e) { if (e.message.includes("acknowledgement failed")) scheduleRetryLogin(); } Prevention
- Ensure the instance is healthy before initiating login
- Avoid running login during instance deploys or network maintenance
- Whitelist the acknowledgement endpoint in proxies/firewalls
When it happens
Trigger: acknowledgeDeviceGrant exhausts all 5 attempts because the instance's acknowledgement endpoint keeps returning network errors/exceptions or 5xx/429 statuses, or returns a non-retryable error on the final attempt.
Common situations: Private instance briefly down or deploying during login; corporate proxy intercepting the acknowledgement POST; rate limiting (429) persisting past the retry window; DNS or TLS problems reaching the instance.
Understand the failure class
Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.
Related errors
- device authorization failed: HTTP
- device login polling failed
- 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/f63ad7f930561ebd.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:9659
"content-type": "application/json",
"x-cave-client": "cli",
},
body: JSON.stringify({ device_code: deviceCode, ack_token: ackToken }),
signal: AbortSignal.timeout(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;
// Invalid/expired grants are terminal. Infrastructure responses remain
// retryable so a committed ACK whose response was dropped can converge.
if (response.status < 500 && response.status !== 429) break;
} catch (error) {
lastError = error instanceof Error ? error.message : String(error);
}
if (attempt < 4) await 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`);
}
// 0600 credentials file) — never in plaintext config. organization_id is bound
// 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" },View on GitHub (pinned to 3ee70a1026)