earendil-works/pi · error
Proxy error: ${response.status} ${response.statusText}
Error message
Proxy error: ${response.status} ${response.statusText} What it means
streamProxy POSTs the model/context/options to {proxyUrl}/api/stream with a Bearer authToken; any non-2xx response becomes 'Proxy error: <status> <statusText>', replaced by the server's JSON {error} field when the body parses. The throw happens inside the stream's async body, so per the StreamFn contract it reaches the caller as an error event / final AssistantMessage with stopReason 'error', not as an exception thrown from streamProxy() itself.
Source
Thrown at packages/agent/src/proxy.ts:178
body: JSON.stringify({
model,
context,
options: buildProxyRequestOptions(options),
}),
signal: options.signal,
});
if (!response.ok) {
let errorMessage = `Proxy error: ${response.status} ${response.statusText}`;
try {
const errorData = (await response.json()) as { error?: string };
if (errorData.error) {
errorMessage = `Proxy error: ${errorData.error}`;
}
} catch {
// Couldn't parse error response
}
throw new Error(errorMessage);
}
reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (options.signal?.aborted) {
throw new Error("Request aborted by user");
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
View on GitHub (pinned to 4af9d21d3b)
Solutions
- On 401/403, refresh authToken and retry the stream once
- Verify proxyUrl (scheme, host, /api/stream path) with a direct curl POST
- Check the proxy server's logs for the failing request to see the upstream cause
- If behind a gateway, confirm it forwards the Authorization header and returns JSON errors
Example fix
// before
const stream = streamProxy(model, context, { ...options, authToken, proxyUrl });
// after - consume the final message, refresh token once on 401
let token = await getToken();
for (let attempt = 0; attempt < 2; attempt++) {
const stream = streamProxy(model, context, { ...options, authToken: token, proxyUrl });
const msg = await stream.result;
if (msg.stopReason !== "error" || !/^Proxy error: 40[13]/.test(msg.errorMessage ?? "")) break;
token = await refreshToken();
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!authToken || !proxyUrl) {
throw new Error("streamProxy requires authToken and proxyUrl");
}
const url = new URL(proxyUrl); // throws early on malformed proxyUrl
await fetch(url.origin); // optional liveness probe before the real stream Type guard
const isProxyError = (m: { stopReason?: string; errorMessage?: string }): boolean =>
m.stopReason === "error" && (m.errorMessage ?? "").startsWith("Proxy error:"); Try / catch
// errors arrive on the event stream, not as exceptions
const stream = streamProxy(model, context, opts);
const msg = await stream.result;
if (isProxyError(msg)) {
const status = /Proxy error: (\d{3})/.exec(msg.errorMessage ?? "")?.[1];
if (status === "401" || status === "403") {
authToken = await refreshToken();
return streamProxy(model, context, { ...opts, authToken });
}
throw new Error(msg.errorMessage);
} Prevention
- Refresh auth tokens on a schedule before they expire, not only on failure
- Validate proxyUrl configuration at startup
- Ensure gateways forward the Authorization header and emit JSON error bodies
- Log proxy status codes with request ids to correlate with server logs
When it happens
Trigger: Expired or invalid authToken producing 401/403; wrong or truncated proxyUrl hitting 404/502 from the wrong host; the proxy server failing to reach the upstream LLM provider and returning 500 with its own error message; a gateway in front returning an HTML error page so the JSON override never applies.
Common situations: Long-running apps whose token expires mid-session because refresh was never wired; proxyUrl pointing at a different deployment or missing the /api/stream route; nginx/Cloudflare stripping the Authorization header or returning non-JSON error bodies.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24).
Data as JSON: /api/errors/4057dee0445e859e.
Report an issue: GitHub.