Problem
When the Anthropic upstream returns a 4xx/5xx with its own error envelope (e.g. `{"type":"error","error":{"type":"overloaded_error","message":"..."}}`), the gateway preserves the body verbatim (truncated to 1024 chars) inside the OpenAI-shaped `error.message` it returns to the client. This leaks Anthropic-specific error type tokens (`overloaded_error`, `invalid_request_error`, etc.) into a response that is otherwise an OpenAI-shaped envelope.
Reproduce
```rust
// Mock Anthropic upstream returns 503 with anthropic shape
.respond_with(ResponseTemplate::new(503).set_body_string(
r#"{"type":"error","error":{"type":"overloaded_error","message":"upstream busy"}}"#,
))
// ... gateway 502s, returns OpenAI envelope
let v = body_json(resp);
assert_eq!(v["error"]["type"], "upstream_error"); // ✅
let msg = v["error"]["message"].as_str().unwrap();
assert!(!msg.contains("overloaded_error"), "taxonomy leaked"); // ❌ — leaks
```
Root cause
`crates/aisix-provider-anthropic/src/bridge.rs::map_http_error`:
```rust
async fn map_http_error(status: StatusCode, resp: reqwest::Response) -> BridgeError {
let message = resp.text().await.unwrap_or_default();
BridgeError::UpstreamStatus {
status: status.as_u16(),
message: truncate(&message, 1024),
}
}
```
The raw upstream body is stored as the `message` and surfaces unchanged on the client-facing error response.
Suggested fix
Parse the upstream error envelope per provider, extract just the human-readable `message` field, and discard the upstream's `type` / `code` taxonomy. For Anthropic specifically, parse `{type:"error", error:{type, message}}` and keep only `error.message`. For unparseable bodies, fall back to a generic "upstream returned status N" string rather than the raw payload.
Why this matters
Clients write error-handling code against the gateway's documented error taxonomy. Provider-shape strings appearing in messages either confuse them (which type system?) or make their handling provider-specific against a gateway that promises uniformity.
This was flagged in an independent test-audit pass over PR #129. The audit suggested asserting "no provider-shape strings in error.message", but the assertion would fail on `main` — the gap is in the gateway, not the test. Filing here so the feature work is tracked separately.
Refs #127.
Problem
When the Anthropic upstream returns a 4xx/5xx with its own error envelope (e.g. `{"type":"error","error":{"type":"overloaded_error","message":"..."}}`), the gateway preserves the body verbatim (truncated to 1024 chars) inside the OpenAI-shaped `error.message` it returns to the client. This leaks Anthropic-specific error type tokens (`overloaded_error`, `invalid_request_error`, etc.) into a response that is otherwise an OpenAI-shaped envelope.
Reproduce
```rust
// Mock Anthropic upstream returns 503 with anthropic shape
.respond_with(ResponseTemplate::new(503).set_body_string(
r#"{"type":"error","error":{"type":"overloaded_error","message":"upstream busy"}}"#,
))
// ... gateway 502s, returns OpenAI envelope
let v = body_json(resp);
assert_eq!(v["error"]["type"], "upstream_error"); // ✅
let msg = v["error"]["message"].as_str().unwrap();
assert!(!msg.contains("overloaded_error"), "taxonomy leaked"); // ❌ — leaks
```
Root cause
`crates/aisix-provider-anthropic/src/bridge.rs::map_http_error`:
```rust
async fn map_http_error(status: StatusCode, resp: reqwest::Response) -> BridgeError {
let message = resp.text().await.unwrap_or_default();
BridgeError::UpstreamStatus {
status: status.as_u16(),
message: truncate(&message, 1024),
}
}
```
The raw upstream body is stored as the `message` and surfaces unchanged on the client-facing error response.
Suggested fix
Parse the upstream error envelope per provider, extract just the human-readable `message` field, and discard the upstream's `type` / `code` taxonomy. For Anthropic specifically, parse `{type:"error", error:{type, message}}` and keep only `error.message`. For unparseable bodies, fall back to a generic "upstream returned status N" string rather than the raw payload.
Why this matters
Clients write error-handling code against the gateway's documented error taxonomy. Provider-shape strings appearing in messages either confuse them (which type system?) or make their handling provider-specific against a gateway that promises uniformity.
This was flagged in an independent test-audit pass over PR #129. The audit suggested asserting "no provider-shape strings in error.message", but the assertion would fail on `main` — the gap is in the gateway, not the test. Filing here so the feature work is tracked separately.
Refs #127.