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

  1. Retry `login` once the instance is reachable — credentials are already stored locally
  2. Check instance health/logs for errors on the delivery-acknowledgement endpoint
  3. Verify network/proxy allows POSTs to the instance's acknowledgement URL
  4. 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

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


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)