vercel/ai · warning · RelayRequestError

Invalid host tool catalog poll request.

Error message

Invalid host tool catalog poll request.

What it means

The /catalog/next long-poll endpoint requires a JSON body that is an object with an afterRevision field that is a safe non-negative integer. Anything else — missing field, non-integer, negative, or a non-object body — yields this 400 RelayRequestError before the poll is evaluated.

Source

Thrown at packages/harness-acp/src/v1/bridge/host-tool-relay.ts:211

  throw new RelayRequestError({
    status: 404,
    message: 'Unknown host tool relay endpoint.',
  });
}

async function handleCatalogNext({
  body,
  state,
}: {
  body: unknown;
  state: CatalogState;
}): Promise<unknown> {
  if (
    !isRecord(body) ||
    !Number.isSafeInteger(body.afterRevision) ||
    (body.afterRevision as number) < 0
  ) {
    throw new RelayRequestError({
      status: 400,
      message: 'Invalid host tool catalog poll request.',
    });
  }
  const afterRevision = body.afterRevision as number;
  if (!state.closed && afterRevision >= state.revision) {
    await waitForCatalogChange({ state });
  }
  if (state.closed) {
    return { closed: true, revision: state.revision };
  }
  return afterRevision < state.revision
    ? { revision: state.revision, tools: state.tools }
    : { revision: state.revision };
}

function handleCatalogSeen({
  body,

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Send { afterRevision: <non-negative safe integer> } — use 0 for the initial poll.
  2. Ensure the body is JSON with content-type application/json and no extra top-level type mismatch (must be an object).
  3. Validate the value client-side with Number.isSafeInteger and >= 0 before sending.
  4. If revisions grow beyond safe integers in your client, stop and re-sync rather than casting.

Example fix

// before
body: JSON.stringify({ revision: lastRev })
// after
body: JSON.stringify({ afterRevision: Number.isSafeInteger(lastRev) && lastRev >= 0 ? lastRev : 0 })
Defensive patterns

Strategy: validation

Validate before calling

function catalogPollBody(afterRevision: unknown) {
  if (!Number.isSafeInteger(afterRevision) || (afterRevision as number) < 0) {
    throw new Error('afterRevision must be a non-negative safe integer');
  }
  return JSON.stringify({ afterRevision });
}

Type guard

function isValidPollBody(body: unknown): body is { afterRevision: number } {
  return body != null && typeof body === 'object' && !Array.isArray(body) &&
    Number.isSafeInteger((body as any).afterRevision) &&
    (body as any).afterRevision >= 0;
}

Prevention

When it happens

Trigger: Polling /catalog/next with an empty body, with { revision: 0 } instead of { afterRevision: 0 }, with afterRevision as a string ('0'), a float, null, or a negative number, or sending non-JSON content.

Common situations: Hand-rolled polling clients using the wrong field name; JSON that lost integer precision (afterRevision sent as 1.0 string); test scripts hitting the endpoint directly without the expected payload shape; serializing undefined so the field is dropped.

Related errors


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