vercel/ai · error · RelayRequestError
Host tool relay request is not valid JSON.
Error message
Host tool relay request is not valid JSON.
What it means
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.
Source
Thrown at packages/harness-acp/src/v1/bridge/host-tool-relay.ts:457
let size = 0;
for await (const chunk of request) {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
size += buffer.length;
if (size > 16 * 1024 * 1024) {
throw new RelayRequestError({
status: 413,
message: 'Host tool relay request is too large.',
});
}
chunks.push(buffer);
}
const text = Buffer.concat(chunks).toString('utf8');
try {
return await new Response(text, {
headers: { 'content-type': 'application/json' },
}).json();
} catch {
throw new RelayRequestError({
status: 400,
message: 'Host tool relay request is not valid JSON.',
});
}
}
function credentialsMatch({
expected,
actual,
}: {
expected: string;
actual: string | undefined;
}): boolean {
const expectedValue = Buffer.from(`Bearer ${expected}`);
const actualValue = Buffer.from(actual ?? '');
return (
expectedValue.length === actualValue.length &&
timingSafeEqual(expectedValue, actualValue)View on GitHub (pinned to 69428b1f8b)
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.
Example fix
// before
await fetch(relayUrl, { method: 'POST', body: params }); // params is an object
// after
await fetch(relayUrl, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(params),
}); Defensive patterns
Strategy: validation
Validate before calling
import { safeParseJSON } from '@ai-sdk/provider-utils';
const text = JSON.stringify(body);
if (safeParseJSON(text) == null) throw new Error('Relay body is not valid JSON'); Try / catch
try {
await postToRelay(body);
} catch (error) {
if (RelayRequestError.isInstance(error) && error.status === 400) {
// log the serialized body and fix serialization
}
throw error;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to process successful response
- Invalid input for tool ${toolName}: ${getErrorMessage(cause)
- Failed to fetch the response.
- The response body is empty.
- ${readErrorMessage({ value, status: response.status })}
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/05d7fac791518abd.
Report an issue: GitHub.