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

  1. Ack only with revision values previously returned by /catalog/next from this same relay instance.
  2. Send the exact field name revision as a safe integer >= 1.
  3. On restart, reset your client's revision state to 0 and re-poll /catalog/next.
  4. 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

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


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/64b62cce7e8dedaa. Report an issue: GitHub.