{"record":{"id":"64b62cce7e8dedaa","repo":"vercel/ai","slug":"invalid-host-tool-catalog-acknowledgment","errorCode":null,"errorMessage":"Invalid host tool catalog acknowledgment.","messagePattern":"Invalid host tool catalog acknowledgment\\.","errorType":"http","errorClass":"RelayRequestError","httpStatus":400,"severity":"warning","filePath":"packages/harness-acp/src/v1/bridge/host-tool-relay.ts","lineNumber":241,"sourceCode":"  return afterRevision < state.revision\n    ? { revision: state.revision, tools: state.tools }\n    : { revision: state.revision };\n}\n\nfunction handleCatalogSeen({\n  body,\n  state,\n}: {\n  body: unknown;\n  state: CatalogState;\n}): unknown {\n  if (\n    !isRecord(body) ||\n    !Number.isSafeInteger(body.revision) ||\n    (body.revision as number) < 1 ||\n    (body.revision as number) > state.revision\n  ) {\n    throw new RelayRequestError({\n      status: 400,\n      message: 'Invalid host tool catalog acknowledgment.',\n    });\n  }\n  state.servedRevision = Math.max(\n    state.servedRevision,\n    body.revision as number,\n  );\n  resolveRefreshWaiters({ state, closing: false });\n  return { acknowledged: true };\n}\n\nasync function handleInvocation({\n  body,\n  state,\n  serverName,\n  turn,\n  nextInvocationOrder,","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness-acp/src/v1/bridge/host-tool-relay.ts#L223-L259","documentation":"The /catalog/seen endpoint acknowledges that a client has served tools up to a given catalog revision. The body must be an object with a revision that is a safe integer between 1 and the relay's current state.revision. Out-of-range, zero, negative, non-integer, or missing values produce this 400 RelayRequestError.","triggerScenarios":"Acknowledging with revision 0 before any catalog was served, acknowledging a revision greater than the current relay revision (e.g. computed from another relay instance), sending { rev: n } with the wrong field name, or sending a string/float revision.","commonSituations":"Client caches revisions from a previous relay process and posts a newer revision than the current one; field-name mismatch in a custom client; race where the client acks a revision before updateCatalog bumped it on this relay; unit tests seeding bad values.","solutions":["Ack only with revision values previously returned by /catalog/next from this same relay instance.","Send the exact field name revision as a safe integer >= 1.","On restart, reset your client's revision state to 0 and re-poll /catalog/next.","Clamp/guard client-side: only ack when revision <= the last revision you actually received from this relay."],"exampleFix":"// before\nawait post(seenUrl, { revision: localRev + 1 });\n// after\nif (localRev >= 1 && localRev <= lastServedRevision) {\n  await post(seenUrl, { revision: localRev });\n}","handlingStrategy":"validation","validationCode":"function catalogSeenBody(revision: unknown, relayRevision: number) {\n  if (!Number.isSafeInteger(revision) || (revision as number) < 1 || (revision as number) > relayRevision) {\n    throw new Error(`revision must be an integer in [1, ${relayRevision}]`);\n  }\n  return JSON.stringify({ revision });\n}","typeGuard":"function isValidSeenBody(body: unknown, relayRevision: number): body is { revision: number } {\n  return body != null && typeof body === 'object' && !Array.isArray(body) &&\n    Number.isSafeInteger((body as any).revision) &&\n    (body as any).revision >= 1 &&\n    (body as any).revision <= relayRevision;\n}","tryCatchPattern":null,"preventionTips":["Ack only revisions previously returned by this relay's /catalog/next.","Reset local revision state to 0 after a relay restart.","Never ack a revision you have not actually served to the consumer.","Keep revision state per-relay-instance, not global."],"tags":["validation","relay","catalog","harness-acp"],"backgroundTag":"request-body-validation-failed","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}