{"record":{"id":"05d7fac791518abd","repo":"vercel/ai","slug":"host-tool-relay-request-is-not-valid-json","errorCode":null,"errorMessage":"Host tool relay request is not valid JSON.","messagePattern":"Host tool relay request is not valid JSON\\.","errorType":"http","errorClass":"RelayRequestError","httpStatus":400,"severity":"error","filePath":"packages/harness-acp/src/v1/bridge/host-tool-relay.ts","lineNumber":457,"sourceCode":"  let size = 0;\n  for await (const chunk of request) {\n    const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n    size += buffer.length;\n    if (size > 16 * 1024 * 1024) {\n      throw new RelayRequestError({\n        status: 413,\n        message: 'Host tool relay request is too large.',\n      });\n    }\n    chunks.push(buffer);\n  }\n  const text = Buffer.concat(chunks).toString('utf8');\n  try {\n    return await new Response(text, {\n      headers: { 'content-type': 'application/json' },\n    }).json();\n  } catch {\n    throw new RelayRequestError({\n      status: 400,\n      message: 'Host tool relay request is not valid JSON.',\n    });\n  }\n}\n\nfunction credentialsMatch({\n  expected,\n  actual,\n}: {\n  expected: string;\n  actual: string | undefined;\n}): boolean {\n  const expectedValue = Buffer.from(`Bearer ${expected}`);\n  const actualValue = Buffer.from(actual ?? '');\n  return (\n    expectedValue.length === actualValue.length &&\n    timingSafeEqual(expectedValue, actualValue)","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness-acp/src/v1/bridge/host-tool-relay.ts#L439-L475","documentation":"readJSONBody concatenates the raw request chunks and parses them with Response.json(); when parsing fails it throws RelayRequestError with HTTP status 400. The relay endpoint only accepts well-formed JSON request bodies, so any malformed body is rejected before it reaches tool dispatch. This surfaces protocol misuse by the caller of the relay endpoint rather than a tool failure.","triggerScenarios":"A request to the host tool relay endpoint whose body is empty, truncated, not UTF-8 JSON (e.g. form-encoded or binary data), or syntactically invalid JSON, causing Response(...).json() to throw inside readJSONBody.","commonSituations":"Client sends URL-encoded or multipart body instead of application/json; request stream cut off mid-write producing truncated JSON; manual fetch calls constructing the body with string concatenation that produces invalid JSON (unquoted keys, trailing commas); sending NDJSON lines that are not a single JSON document.","solutions":["Serialize the request body with JSON.stringify and set the content-type header to application/json before posting to the relay.","Log/inspect the exact body text being sent and validate it with safeParseJSON before sending.","Check that the client fully writes the body and does not abort mid-stream, producing truncated JSON."],"exampleFix":"// before\nawait fetch(relayUrl, { method: 'POST', body: params }); // params is an object\n// after\nawait fetch(relayUrl, {\n  method: 'POST',\n  headers: { 'content-type': 'application/json' },\n  body: JSON.stringify(params),\n});","handlingStrategy":"validation","validationCode":"import { safeParseJSON } from '@ai-sdk/provider-utils';\nconst text = JSON.stringify(body);\nif (safeParseJSON(text) == null) throw new Error('Relay body is not valid JSON');","typeGuard":null,"tryCatchPattern":"try {\n  await postToRelay(body);\n} catch (error) {\n  if (RelayRequestError.isInstance(error) && error.status === 400) {\n    // log the serialized body and fix serialization\n  }\n  throw error;\n}","preventionTips":["Always build the body with JSON.stringify and set content-type: application/json.","Round-trip JSON.parse(JSON.stringify(body)) in tests to guarantee serializability.","Avoid hand-built JSON strings and NDJSON to the relay endpoint."],"tags":["http","json","bad-request"],"backgroundTag":"invalid-json-body","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}