{"record":{"id":"84088d0a0d9c861e","repo":"vercel/ai","slug":"invalid-host-tool-relay-credential","errorCode":null,"errorMessage":"Invalid host tool relay credential.","messagePattern":"Invalid host tool relay credential\\.","errorType":"http","errorClass":"RelayRequestError","httpStatus":401,"severity":"error","filePath":"packages/harness-acp/src/v1/bridge/host-tool-relay.ts","lineNumber":172,"sourceCode":"  credential: string;\n  state: CatalogState;\n  serverName: string;\n  turn: HostToolRelayTurn | undefined;\n  nextInvocationOrder: () => number;\n}): Promise<unknown> {\n  if (request.method !== 'POST') {\n    throw new RelayRequestError({\n      status: 404,\n      message: 'Unknown host tool relay endpoint.',\n    });\n  }\n  if (\n    !credentialsMatch({\n      expected: credential,\n      actual: request.headers.authorization,\n    })\n  ) {\n    throw new RelayRequestError({\n      status: 401,\n      message: 'Invalid host tool relay credential.',\n    });\n  }\n  const body = await readJSONBody({ request });\n  if (request.url === '/catalog/next') {\n    return handleCatalogNext({ body, state });\n  }\n  if (request.url === '/catalog/seen') {\n    return handleCatalogSeen({ body, state });\n  }\n  if (request.url === '/invoke') {\n    return handleInvocation({\n      body,\n      state,\n      serverName,\n      turn,\n      nextInvocationOrder,","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness-acp/src/v1/bridge/host-tool-relay.ts#L154-L190","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set the header to exactly `Bearer ${credential}` using the credential from the same startHostToolRelay call that produced the URL.","Check the header is not being stripped by middleware, proxy, or fetch redirect (credentials are not forwarded cross-origin).","If the host process restarted, re-fetch a fresh URL/credential pair instead of reusing cached ones.","Debug by logging request.headers.authorization on the client side to confirm the exact value sent."],"exampleFix":"// before\nheaders: { authorization: credential }\n// after\nheaders: { authorization: `Bearer ${relay.credential}` }","handlingStrategy":"try-catch","validationCode":"function assertCredential(relay: { url: string; credential: string }) {\n  if (typeof relay.credential !== 'string' || relay.credential.length === 0) {\n    throw new Error('Missing relay credential — use the value returned by startHostToolRelay.');\n  }\n}","typeGuard":"function hasAuthHeader(headers: Record<string, string>): boolean {\n  return typeof headers.authorization === 'string' &&\n    headers.authorization.startsWith('Bearer ') &&\n    headers.authorization.length > 'Bearer '.length;\n}","tryCatchPattern":"const res = await fetch(url, { headers: { authorization: `Bearer ${credential}` } });\nif (res.status === 401) {\n  const { error } = await res.json();\n  if (error === 'Invalid host tool relay credential.') {\n    // re-acquire url+credential from the current relay instance and retry\n  }\n}","preventionTips":["Always read url and credential from the same HostToolRelay object — never hardcode.","Include the 'Bearer ' prefix exactly.","After host restart, re-fetch the credential instead of caching it.","Check proxies/middleware for Authorization header stripping before debugging the library."],"tags":["auth","http","relay","harness-acp","security"],"backgroundTag":"invalid-bearer-token","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}