{"record":{"id":"5a8cb199a656e44e","repo":"JuliusBrussee/caveman","slug":"device-login-failed-server-did-not-provide-a-deli-5a8cb1","errorCode":null,"errorMessage":"device login failed: server did not provide a delivery acknowledgement token","messagePattern":"device login failed: server did not provide a delivery acknowledgement token","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/device-auth/src/index.ts","lineNumber":57,"sourceCode":"}\n\nasync function acknowledge(\n  options: {\n    baseURL: string;\n    client: string;\n    code: DeviceCode;\n    credentials: DeviceCredentials;\n    fetcher: typeof globalThis.fetch;\n    signal?: AbortSignal;\n    sleep: (ms: number) => Promise<void>;\n  },\n): Promise<void> {\n  const durable = Boolean(options.credentials.refresh_token || options.credentials.gateway_api_key ||\n    options.credentials.gateway_key_id || options.credentials.project_id);\n  if (!durable) return;\n  const ackToken = options.credentials.delivery_ack_token;\n  if (typeof ackToken !== \"string\" || ackToken === \"\") {\n    throw new Error(\"device login failed: server did not provide a delivery acknowledgement token\");\n  }\n  let lastError = \"unknown error\";\n  for (let attempt = 0; attempt < 5; attempt++) {\n    try {\n      const response = await options.fetcher(`${options.baseURL}/api/v1/auth/device/ack`, {\n        method: \"POST\",\n        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;","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/df2ccd85c94ec3c8289cb62ac020d241ccfb0c60/packages/device-auth/src/index.ts#L39-L75","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (partial persist drops the token)\nsaveCredentials({ refresh_token: creds.refresh_token, project_id: creds.project_id });\n// after\nsaveCredentials(creds); // keep delivery_ack_token alongside the durable fields","handlingStrategy":"validation","validationCode":"const creds = loginResult.credentials;\nconst durable = Boolean(creds.refresh_token || creds.gateway_api_key || creds.gateway_key_id || creds.project_id);\nif (durable && (typeof creds.delivery_ack_token !== \"string\" || creds.delivery_ack_token === \"\"))\n  throw new Error(\"server response missing delivery_ack_token; upgrade client/server\");","typeGuard":"function hasAckToken(c: Record<string, unknown>): c is { delivery_ack_token: string } & Record<string, unknown> {\n  return typeof c.delivery_ack_token === \"string\" && c.delivery_ack_token !== \"\";\n}","tryCatchPattern":"try {\n  await acknowledge(options);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"delivery acknowledgement token\")) {\n    console.error(\"Auth server did not return delivery_ack_token — check server/client versions or dev stub\");\n  } else throw e;\n}","preventionTips":["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."],"tags":["device-auth","missing-field","api-contract","token"],"backgroundTag":"missing-ack-token","analyzedSha":"df2ccd85c94ec3c8289cb62ac020d241ccfb0c60","analyzedAt":"2026-08-31T22:10:17.934Z","contentChangedAt":"2026-08-31T22:10:17.934Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}