JuliusBrussee/caveman · error · Error
device login failed: server did not provide a delivery ackno
Error message
device login failed: server did not provide a delivery acknowledgement token
What it means
After a device login, acknowledge() only validates the delivery-ack token when durable credentials exist (refresh token, gateway key material, or project id). If the server's response omitted delivery_ack_token, the library refuses to proceed because it cannot prove delivery of the credentials to the server, which would otherwise let the server revoke them.
Source
Thrown at packages/device-auth/src/index.ts:57
}
async function acknowledge(
options: {
baseURL: string;
client: string;
code: DeviceCode;
credentials: DeviceCredentials;
fetcher: typeof globalThis.fetch;
signal?: AbortSignal;
sleep: (ms: number) => Promise<void>;
},
): Promise<void> {
const durable = Boolean(options.credentials.refresh_token || options.credentials.gateway_api_key ||
options.credentials.gateway_key_id || options.credentials.project_id);
if (!durable) return;
const ackToken = options.credentials.delivery_ack_token;
if (typeof ackToken !== "string" || ackToken === "") {
throw new Error("device login failed: server did not provide a delivery acknowledgement token");
}
let lastError = "unknown error";
for (let attempt = 0; attempt < 5; attempt++) {
try {
const response = await options.fetcher(`${options.baseURL}/api/v1/auth/device/ack`, {
method: "POST",
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;View on GitHub (pinned to df2ccd85c9)
Solutions
- Check what the token endpoint returned (log/inspect the response JSON) — confirm whether delivery_ack_token is present.
- Upgrade or downgrade the device-auth client and server so both agree on the delivery_ack_token field.
- If running against a dev/mock server, make it return a delivery_ack_token in the credential response.
- Make credentials non-durable (no refresh_token/gateway_api_key/gateway_key_id/project_id) if you truly don't need durable delivery — acknowledge() then becomes a no-op.
- Ensure you don't overwrite credentials with a partial object that drops delivery_ack_token after login.
Example fix
// before (partial persist drops the token)
saveCredentials({ refresh_token: creds.refresh_token, project_id: creds.project_id });
// after
saveCredentials(creds); // keep delivery_ack_token alongside the durable fields Defensive patterns
Strategy: validation
Validate before calling
const creds = loginResult.credentials;
const durable = Boolean(creds.refresh_token || creds.gateway_api_key || creds.gateway_key_id || creds.project_id);
if (durable && (typeof creds.delivery_ack_token !== "string" || creds.delivery_ack_token === ""))
throw new Error("server response missing delivery_ack_token; upgrade client/server"); Type guard
function hasAckToken(c: Record<string, unknown>): c is { delivery_ack_token: string } & Record<string, unknown> {
return typeof c.delivery_ack_token === "string" && c.delivery_ack_token !== "";
} Try / catch
try {
await acknowledge(options);
} catch (e) {
if (e instanceof Error && e.message.includes("delivery acknowledgement token")) {
console.error("Auth server did not return delivery_ack_token — check server/client versions or dev stub");
} else throw e;
} Prevention
- Keep device-auth client and server versions in sync.
- Persist the full credentials object so delivery_ack_token isn't dropped.
- Use production-like fixtures in dev stub servers including delivery_ack_token.
- Log the token-endpoint response shape once at integration time to catch contract drift early.
When it happens
Trigger: Calling acknowledge() (directly or as part of the device flow) with durable credentials while options.credentials.delivery_ack_token is missing, an empty string, or not a string — i.e. the token-exchange server response did not include the field.
Common situations: Server/client version mismatch where the server no longer returns delivery_ack_token; a mock or stub auth server used in dev that omits the field; persisting credentials but dropping the token during JSON round-tripping; truncated API response fields.
Related errors
- device authorization failed: ${JSON.stringify(rawCode)}
- cave_internal_run_option
- cave artifact response missing artifact_id
- device credential delivery acknowledgement failed (${lastErr
- device authorization failed: HTTP ${codeResponse.status}
AI-assisted analysis of JuliusBrussee/caveman@df2ccd85c9 (2026-08-31).
Data as JSON: /api/errors/5a8cb199a656e44e.
Report an issue: GitHub.