vercel/ai · error
unauthorized tool relay request
Error message
unauthorized tool relay request
What it means
The Codex harness tool relay is a local HTTP server that authorizes by requiring exact POST requests to '/'. Any other method or path is rejected with a 401 and the JSON body { error: 'unauthorized tool relay request' }. It is an authorization gate protecting the relay endpoint.
Source
Thrown at packages/harness-codex/src/bridge/tool-relay.ts:33
tools,
emit,
requestToolResult,
authorizer = new ToolRelayAuthorizer(),
}: {
tools: ReadonlyArray<{ name: string }>;
emit: (message: Record<string, unknown>) => void;
requestToolResult: (
toolCallId: string,
) => Promise<{ output: unknown; isError?: boolean }>;
authorizer?: ToolRelayAuthorizer;
}): Promise<ToolRelay> {
const toolNames = new Set(tools.map(tool => tool.name));
const pendingCalls = new ToolRelayPendingCalls();
const server = createServer(async (req, res) => {
try {
if (req.method !== 'POST' || req.url !== '/') {
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'unauthorized tool relay request' }));
return;
}
const chunks: Buffer[] = [];
for await (const chunk of req) {
chunks.push(chunk as Buffer);
}
const body = Buffer.concat(chunks).toString('utf8');
const { requestId, toolName, input } = JSON.parse(body) as {
requestId: string;
toolName: string;
input: unknown;
};
if (!toolNames.has(toolName)) {
res.writeHead(403, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({ error: `Tool "${toolName}" is not available` }),View on GitHub (pinned to 69428b1f8b)
Solutions
- Send POST requests to exactly '/' on the relay server
- Fix the client/base URL to point at the relay root without extra path segments
- Exclude the relay port from health checks, browser access, or monitoring probes
- Verify the relay is started with startAuthorizedToolRelay and that you are targeting the correct port
Example fix
// before
fetch('http://127.0.0.1:PORT/relay/tools'); // wrong path/method
// after
fetch('http://127.0.0.1:PORT/', { method: 'POST', body: JSON.stringify(payload) }); Defensive patterns
Strategy: validation
Validate before calling
const url = new URL(relayBaseUrl);
if (url.pathname !== '/') throw new Error('tool relay requires POST to /');
// then: fetch(relayBaseUrl, { method: 'POST', body }) Try / catch
const res = await fetch(relayUrl, { method: 'POST', body });
if (res.status === 401) {
const body = await res.json();
throw new Error(`Relay rejected request: ${body.error}`);
} Prevention
- POST only to the relay root path '/'
- Strip trailing paths from the configured relay base URL
- Exclude the relay port from monitoring/health-check probes
When it happens
Trigger: Sending GET/PUT or hitting any path other than '/' on the relay server port; a health check or browser prefetch hitting the server; a misconfigured client URL including a path suffix.
Common situations: Probing the relay port from a browser (GET /favicon.ico); load balancers or monitoring doing GET health checks; tools pointed at a base URL with a trailing path instead of the root endpoint.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- ${readErrorMessage({ value, status: response.status })}
- Unknown host tool relay endpoint.
- Invalid host tool relay credential.
- Failed to fetch the response.
- The response body is empty.
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/bb6184b5e27cefa7.
Report an issue: GitHub.