{"record":{"id":"7fb25220db32bc8e","repo":"vercel/ai","slug":"invalid-host-tool-relay-request","errorCode":null,"errorMessage":"Invalid host tool relay request.","messagePattern":"Invalid host tool relay request\\.","errorType":"http","errorClass":"RelayRequestError","httpStatus":400,"severity":"warning","filePath":"packages/harness-acp/src/v1/bridge/host-tool-relay.ts","lineNumber":284,"sourceCode":"}): Promise<{\n  output: unknown;\n  isError?: boolean;\n  correlationToken: string;\n}> {\n  if (turn == null) {\n    throw new RelayRequestError({\n      status: 409,\n      message: 'No ACP prompt turn is active.',\n    });\n  }\n  if (\n    !isRecord(body) ||\n    typeof body.requestId !== 'string' ||\n    typeof body.toolName !== 'string' ||\n    !isRecord(body.input) ||\n    !Number.isSafeInteger(body.catalogRevision)\n  ) {\n    throw new RelayRequestError({\n      status: 400,\n      message: 'Invalid host tool relay request.',\n    });\n  }\n  if (body.catalogRevision !== state.revision) {\n    throw new RelayRequestError({\n      status: 409,\n      message:\n        `Host tool ${body.toolName} was invoked from stale catalog revision ` +\n        `${body.catalogRevision}; the active revision is ${state.revision}.`,\n    });\n  }\n  const tool = state.tools.find(item => item.name === body.toolName);\n  if (tool == null) {\n    throw new RelayRequestError({\n      status: 404,\n      message:\n        `Host tool ${body.toolName} is not active in catalog revision ` +","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness-acp/src/v1/bridge/host-tool-relay.ts#L266-L302","documentation":"A valid /invoke body must be an object with string requestId, string toolName, a record-valued input, and a safe-integer catalogRevision. If any of these is missing or of the wrong type, handleInvocation throws this 400 RelayRequestError. This is the structural schema check, applied after the active-turn check and before revision/tool checks.","triggerScenarios":"POSTing /invoke with a missing input object, numeric or non-string requestId/toolName, a missing or non-integer catalogRevision, malformed JSON that parses to a non-object, or fields renamed in a custom client.","commonSituations":"Hand-written MCP-to-relay adapters with the wrong payload shape; JSON.stringify dropping undefined fields; catalogRevision sent as a string; frameworks auto-serializing inputs that contain arrays at the top level (rejected by isRecord).","solutions":["Send exactly { requestId: string, toolName: string, input: object, catalogRevision: safe integer }.","Validate the payload client-side before POSTing (types + Number.isSafeInteger on catalogRevision).","Ensure input is a JSON object (not an array or scalar) — serialize tool arguments as a record.","If using a wrapper library, update it to match the current relay wire schema."],"exampleFix":"// before\nbody: JSON.stringify({ id: 123, tool: name, args: input })\n// after\nbody: JSON.stringify({\n  requestId: String(id),\n  toolName: name,\n  input,\n  catalogRevision: currentRevision,\n});","handlingStrategy":"validation","validationCode":"function isValidInvokeBody(body: unknown): body is {\n  requestId: string; toolName: string;\n  input: Record<string, unknown>; catalogRevision: number;\n} {\n  return body != null && typeof body === 'object' && !Array.isArray(body) &&\n    typeof (body as any).requestId === 'string' &&\n    typeof (body as any).toolName === 'string' &&\n    (body as any).input != null && typeof (body as any).input === 'object' && !Array.isArray((body as any).input) &&\n    Number.isSafeInteger((body as any).catalogRevision);\n}","typeGuard":"function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n  return value != null && typeof value === 'object' && !Array.isArray(value);\n}","tryCatchPattern":null,"preventionTips":["Mirror the exact wire schema: requestId, toolName, input, catalogRevision.","Ensure tool inputs serialize as JSON objects, not arrays or scalars.","Send catalogRevision as a number, never a string.","Add a client-side schema assertion unit test for the invoke payload."],"tags":["validation","relay","schema","harness-acp"],"backgroundTag":"request-body-validation-failed","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}