vercel/ai · error · RelayRequestError

Invalid host tool relay credential.

Error message

Invalid host tool relay credential.

What it means

Every relay request must carry an Authorization header equal to 'Bearer ' + the random 32-byte credential returned by startHostToolRelay. The comparison is a timing-safe constant-time check of the exact value (including the Bearer prefix). A missing, malformed, or wrong header produces this 401 RelayRequestError before any routing happens.

Source

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

  credential: string;
  state: CatalogState;
  serverName: string;
  turn: HostToolRelayTurn | undefined;
  nextInvocationOrder: () => number;
}): Promise<unknown> {
  if (request.method !== 'POST') {
    throw new RelayRequestError({
      status: 404,
      message: 'Unknown host tool relay endpoint.',
    });
  }
  if (
    !credentialsMatch({
      expected: credential,
      actual: request.headers.authorization,
    })
  ) {
    throw new RelayRequestError({
      status: 401,
      message: 'Invalid host tool relay credential.',
    });
  }
  const body = await readJSONBody({ request });
  if (request.url === '/catalog/next') {
    return handleCatalogNext({ body, state });
  }
  if (request.url === '/catalog/seen') {
    return handleCatalogSeen({ body, state });
  }
  if (request.url === '/invoke') {
    return handleInvocation({
      body,
      state,
      serverName,
      turn,
      nextInvocationOrder,

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Set the header to exactly `Bearer ${credential}` using the credential from the same startHostToolRelay call that produced the URL.
  2. Check the header is not being stripped by middleware, proxy, or fetch redirect (credentials are not forwarded cross-origin).
  3. If the host process restarted, re-fetch a fresh URL/credential pair instead of reusing cached ones.
  4. Debug by logging request.headers.authorization on the client side to confirm the exact value sent.

Example fix

// before
headers: { authorization: credential }
// after
headers: { authorization: `Bearer ${relay.credential}` }
Defensive patterns

Strategy: try-catch

Validate before calling

function assertCredential(relay: { url: string; credential: string }) {
  if (typeof relay.credential !== 'string' || relay.credential.length === 0) {
    throw new Error('Missing relay credential — use the value returned by startHostToolRelay.');
  }
}

Type guard

function hasAuthHeader(headers: Record<string, string>): boolean {
  return typeof headers.authorization === 'string' &&
    headers.authorization.startsWith('Bearer ') &&
    headers.authorization.length > 'Bearer '.length;
}

Try / catch

const res = await fetch(url, { headers: { authorization: `Bearer ${credential}` } });
if (res.status === 401) {
  const { error } = await res.json();
  if (error === 'Invalid host tool relay credential.') {
    // re-acquire url+credential from the current relay instance and retry
  }
}

Prevention

When it happens

Trigger: Posting to /invoke, /catalog/next, or /catalog/seen without the authorization header, with a token missing the 'Bearer ' prefix, with a credential from a different relay instance, or after restarting the process so the old credential no longer matches.

Common situations: Hardcoding a stale credential instead of using the credential returned from startHostToolRelay; forgetting the Bearer prefix; forwarding requests through a proxy that strips the Authorization header; pointing a client at a second relay instance's URL while using the first instance's credential.

Related errors


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