{"record":{"id":"2ab389acfc4b7ae8","repo":"JuliusBrussee/caveman","slug":"device-credential-delivery-acknowledgement-failed-2ab389","errorCode":null,"errorMessage":"device credential delivery acknowledgement failed (${lastError}); credentials were persisted locally but the server may revoke them after the delivery window","messagePattern":"device credential delivery acknowledgement failed \\((.+?)\\); credentials were persisted locally but the server may revoke them after the delivery window","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/device-auth/src/index.ts","lineNumber":82,"sourceCode":"        headers: {\n          authorization: `Bearer ${options.credentials.access_token}`,\n          \"content-type\": \"application/json\",\n          \"x-cave-client\": options.client,\n        },\n        body: JSON.stringify({ device_code: options.code.device_code, ack_token: ackToken }),\n        signal: requestSignal(options.signal, 5000),\n      });\n      if (response.ok) return;\n      const body = await response.json().catch(() => null) as { error?: { code?: unknown } } | null;\n      const code = typeof body?.error?.code === \"string\" ? body.error.code : `HTTP ${response.status}`;\n      lastError = code;\n      if (response.status < 500 && response.status !== 429) break;\n    } catch (error) {\n      lastError = error instanceof Error ? error.message : String(error);\n    }\n    if (attempt < 4) await options.sleep(Math.min(2000, 200 * 2 ** attempt));\n  }\n  throw new Error(`device credential delivery acknowledgement failed (${lastError}); credentials were persisted locally but the server may revoke them after the delivery window`);\n}\n\nexport async function runCavemanDeviceFlow(options: {\n  baseURL: string;\n  client: string;\n  fetch?: typeof globalThis.fetch;\n  signal?: AbortSignal;\n  sleep?: (ms: number) => Promise<void>;\n  onCode?: (code: DeviceCode) => void | Promise<void>;\n}): Promise<DeviceGrant> {\n  const fetcher = options.fetch ?? globalThis.fetch;\n  const wait = options.sleep ?? defaultSleep;\n  const baseURL = options.baseURL.replace(/\\/$/, \"\");\n  const codeResponse = await fetcher(`${baseURL}/api/v1/auth/device/code`, {\n    method: \"POST\",\n    headers: { \"content-type\": \"application/json\", \"x-cave-client\": options.client },\n    body: \"{}\",\n    signal: requestSignal(options.signal, 5000),","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/df2ccd85c94ec3c8289cb62ac020d241ccfb0c60/packages/device-auth/src/index.ts#L64-L100","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (wrong baseURL, unreachable ack endpoint)\nawait acknowledge({ baseURL: \"https://internal-staging.invalid\", ... });\n// after\nawait acknowledge({ baseURL: \"https://api.example.com\", ... });","handlingStrategy":"retry","validationCode":"// pre-check reachability of the ack endpoint\nconst ping = await fetch(`${baseURL}/api/v1/auth/device/ack`, { method: \"HEAD\" }).catch(() => null);\nif (!ping) throw new Error(\"ack endpoint unreachable; fix network/baseURL before login\");","typeGuard":null,"tryCatchPattern":"try {\n  await acknowledge(options);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"device credential delivery acknowledgement failed\")) {\n    // transient: wait and redo the device login to re-ack fresh credentials\n    await sleep(5000);\n    await redoDeviceFlow();\n  } else throw e;\n}","preventionTips":["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."],"tags":["device-auth","network","retry-exhausted","rate-limit"],"backgroundTag":"ack-delivery-failed","analyzedSha":"df2ccd85c94ec3c8289cb62ac020d241ccfb0c60","analyzedAt":"2026-08-31T22:10:17.934Z","contentChangedAt":"2026-08-31T22:10:17.934Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}