can1357/oh-my-pi · error · ProviderResponseError
anthropic-messages: upstream ${message.stopReason}
Error message
anthropic-messages: upstream ${message.stopReason} What it means
encodeResponse converts an internal AssistantMessage back into an Anthropic Messages API response. If the upstream run ended with stopReason 'error' or 'aborted', it throws ProviderResponseError using the recorded errorMessage, or the fallback 'anthropic-messages: upstream error/aborted'. The server therefore surfaces upstream provider failures to the API client as an error response rather than silently returning an empty/malformed completion.
Source
Thrown at packages/ai/src/providers/anthropic-messages-server.ts:500
blocks.push({ type: "tool_use", id: c.id, name: c.name, input: c.arguments ?? {} });
break;
}
}
return blocks;
}
function encodeUsage(message: AssistantMessage): Record<string, unknown> {
return {
input_tokens: message.usage.input,
output_tokens: message.usage.output,
cache_read_input_tokens: message.usage.cacheRead,
cache_creation_input_tokens: message.usage.cacheWrite,
};
}
export function encodeResponse(message: AssistantMessage, requestedModelId: string): Record<string, unknown> {
if (message.stopReason === "error" || message.stopReason === "aborted") {
throw new AIError.ProviderResponseError(
message.errorMessage ?? `anthropic-messages: upstream ${message.stopReason}`,
{
provider: "anthropic",
kind: "output",
},
);
}
return {
id: message.responseId ?? newMessageId(),
type: "message",
role: "assistant",
model: requestedModelId,
content: encodeContentBlocks(message),
stop_reason: mapStopReasonOut(message.stopReason),
// TODO: surface the matched stop sequence once pi-ai's
// `AssistantMessage.stopReason` carries the matched string. Intentionally
// `null` for now (Anthropic schema allows it).
stop_sequence: null,View on GitHub (pinned to 9690622007)
Solutions
- Inspect message.errorMessage (preferred) over the fallback text — it carries the actual upstream failure reason
- Check upstream provider credentials and quota (the usual cause of stopReason 'error')
- Treat 'upstream aborted' as client cancellation: verify the caller isn't closing the connection prematurely
- Retry transient upstream errors with backoff at the client of this server
- Add health checks/alerting on the upstream provider if these errors appear in clusters
Example fix
// before: no handling of upstream failure responses
const res = await serverClient.post("/v1/messages", body);
return res.json();
// after
const res = await serverClient.post("/v1/messages", body);
if (!res.ok) {
const err = await res.json();
if (isTransientUpstream(err)) return retryWithBackoff(() => post(body));
throw new Error(err.error?.message ?? "upstream provider failure");
} Defensive patterns
Strategy: try-catch
Type guard
import { AIError } from "@oh-my-pi/pi-ai";
function isUpstreamFailure(e: unknown): e is AIError.ProviderResponseError {
return e instanceof AIError.ProviderResponseError;
} Try / catch
try {
return encodeResponse(message, modelId);
} catch (err) {
if (isUpstreamFailure(err)) {
// stopReason was error/aborted upstream; prefer err's contextual info and the
// original message.errorMessage over the generic fallback text
if (message.stopReason === "aborted") return clientCancelledResponse();
return upstreamBadGatewayResponse(message.errorMessage ?? err.message);
}
throw err;
} Prevention
- Monitor downstream provider health; most 'error' stop reasons are upstream auth/quota/outage
- Carry message.errorMessage end-to-end instead of letting the fallback text mask the cause
- Treat 'aborted' as client cancellation and don't retry those
- Retry only classified-transient upstream errors with backoff
When it happens
Trigger: A request proxied through the anthropic-messages server whose downstream provider call produced an AssistantMessage with stopReason 'error' or 'aborted' — provider outage, upstream auth failure, or the original caller aborting mid-stream.
Common situations: The downstream provider API key expired or is invalid; upstream provider is rate-limiting or down; the end client disconnected and the run was aborted; streaming interrupted between server and upstream provider.
Related errors
- anthropic-messages: ${data.summary}
- Received text_delta for non-text content
- Received text_end for non-text content
- Request was aborted.
- AnthropicConnectionError
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e4d256f0f750a4c7.
Report an issue: GitHub.