vercel/ai · warning · RelayRequestError
Invalid host tool catalog acknowledgment.
Error message
Invalid host tool catalog acknowledgment.
What it means
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.
Source
Thrown at packages/harness-acp/src/v1/bridge/host-tool-relay.ts:241
return afterRevision < state.revision
? { revision: state.revision, tools: state.tools }
: { revision: state.revision };
}
function handleCatalogSeen({
body,
state,
}: {
body: unknown;
state: CatalogState;
}): unknown {
if (
!isRecord(body) ||
!Number.isSafeInteger(body.revision) ||
(body.revision as number) < 1 ||
(body.revision as number) > state.revision
) {
throw new RelayRequestError({
status: 400,
message: 'Invalid host tool catalog acknowledgment.',
});
}
state.servedRevision = Math.max(
state.servedRevision,
body.revision as number,
);
resolveRefreshWaiters({ state, closing: false });
return { acknowledged: true };
}
async function handleInvocation({
body,
state,
serverName,
turn,
nextInvocationOrder,View on GitHub (pinned to 69428b1f8b)
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.
Example fix
// before
await post(seenUrl, { revision: localRev + 1 });
// after
if (localRev >= 1 && localRev <= lastServedRevision) {
await post(seenUrl, { revision: localRev });
} Defensive patterns
Strategy: validation
Validate before calling
function catalogSeenBody(revision: unknown, relayRevision: number) {
if (!Number.isSafeInteger(revision) || (revision as number) < 1 || (revision as number) > relayRevision) {
throw new Error(`revision must be an integer in [1, ${relayRevision}]`);
}
return JSON.stringify({ revision });
} Type guard
function isValidSeenBody(body: unknown, relayRevision: number): body is { revision: number } {
return body != null && typeof body === 'object' && !Array.isArray(body) &&
Number.isSafeInteger((body as any).revision) &&
(body as any).revision >= 1 &&
(body as any).revision <= relayRevision;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid host tool catalog poll request.
- Invalid host tool relay request.
- Host tool ${body.toolName} was invoked from stale catalog re
- Host tool ${body.toolName} is not active in catalog revision
- Invalid host tool catalog poll response.
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/64b62cce7e8dedaa.
Report an issue: GitHub.