Repository navigation
fix(gateway): openai-compatible error envelopes - #2532
Conversation
Gateway-level errors (auth, usage/rate limit, validation, timeouts) were
returned as `{error: true, status, message}` instead of OpenAI's
`{error: {message, type, param, code}}`, breaking OpenAI SDK clients.
Route the global error handler and the remaining ad-hoc error returns
through shared builders: OpenAI shape for chat/embeddings/images/etc and
Anthropic's `{type: "error", error: {type, message}}` for /v1/messages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughAdds OpenAI/Anthropic error-body builders, refactors app.onError to emit provider-shaped error envelopes, updates Anthropic and Moderations handlers to use the builders, and adjusts API/E2E tests and docs to expect the structured ChangesStandardized Gateway Error Responses
Sequence Diagram(s) sequenceDiagram
participant Client
participant Router
participant appOnError
participant renderGatewayError
participant buildOpenAIErrorBody
participant buildAnthropicErrorBody
Client->>Router: request triggers handler (may throw)
Router->>appOnError: error delivered
appOnError->>renderGatewayError: renderGatewayError(ctx, error, status)
alt path starts with /v1/messages
renderGatewayError->>buildAnthropicErrorBody: buildAnthropicErrorBody(message, status)
buildAnthropicErrorBody-->>renderGatewayError: Anthropic envelope
else
renderGatewayError->>buildOpenAIErrorBody: buildOpenAIErrorBody(message, status)
buildOpenAIErrorBody-->>renderGatewayError: OpenAI envelope
end
renderGatewayError-->>appOnError: standardized envelope
appOnError-->>Router: c.json(response)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Standardizes gateway-level error responses so OpenAI-compatible endpoints always return an OpenAI-style error envelope, while the Anthropic Messages endpoint (/v1/messages) returns an Anthropic-style error envelope. This improves SDK compatibility and removes remaining ad-hoc error shapes.
Changes:
- Added shared error envelope builders + status→type/code mappings (
buildOpenAIErrorBody,buildAnthropicErrorBody). - Updated the global gateway error handler to render errors via these builders (path-aware for
/v1/messages). - Updated/modded affected endpoints and tests to assert
json.error.messageand the OpenAI error object shape.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| apps/gateway/src/moderations/moderations.ts | Uses the shared OpenAI error envelope builder for moderation upstream/fallback error responses. |
| apps/gateway/src/lib/error-response.ts | Introduces shared OpenAI/Anthropic error envelope builders and HTTP status mappings. |
| apps/gateway/src/lib/error-response.spec.ts | Adds unit tests covering envelope construction and status mappings. |
| apps/gateway/src/chat-custom-provider.e2e.ts | Updates E2E assertions to expect OpenAI envelope (json.error.message). |
| apps/gateway/src/app.ts | Centralizes gateway error rendering via the new builders and switches envelope based on request path (/v1/messages vs others). |
| apps/gateway/src/api.spec.ts | Updates assertions to expect OpenAI envelope and strengthens missing-Authorization expectations. |
| apps/gateway/src/anthropic/anthropic.ts | Migrates non-streaming Anthropic passthrough errors to the Anthropic envelope builder. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/gateway/src/moderations/moderations.ts (1)
133-140:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNormalize all moderation error payloads to OpenAI envelope and align schema nullability.
This branch still returns raw
upstreamJsonobjects when non-string, which can break the standardized{ error: { message, type, param, code } }contract. Also,buildOpenAIErrorBodycan emitcode: null, butmoderationErrorSchemacurrently requirescode: string.🧩 Proposed fix
const moderationErrorSchema = z.object({ error: z.object({ message: z.string(), type: z.string(), param: z.string().nullable(), - code: z.string(), + code: z.string().nullable(), }), });- return c.json( - (typeof upstreamJson === "string" - ? buildOpenAIErrorBody({ - message: upstreamJson, - status: upstreamResponse.status, - }) - : upstreamJson) ?? - buildOpenAIErrorBody({ - message: "An error occurred", - status: upstreamResponse.status, - }), + const upstreamError = + typeof upstreamJson === "object" && + upstreamJson !== null && + "error" in upstreamJson + ? (upstreamJson as { + error?: { + message?: unknown; + type?: unknown; + param?: unknown; + code?: unknown; + }; + }).error + : undefined; + + const message = + typeof upstreamError?.message === "string" + ? upstreamError.message + : typeof upstreamJson === "string" + ? upstreamJson + : "An error occurred"; + + return c.json( + buildOpenAIErrorBody({ + message, + status: upstreamResponse.status, + type: typeof upstreamError?.type === "string" ? upstreamError.type : undefined, + param: + typeof upstreamError?.param === "string" || upstreamError?.param === null + ? upstreamError.param + : undefined, + code: + typeof upstreamError?.code === "string" || upstreamError?.code === null + ? upstreamError.code + : undefined, + }), upstreamResponse.status as | 400 | 401 | 403Also applies to: 654-663
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/gateway/src/moderations/moderations.ts` around lines 133 - 140, The moderation handler currently returns raw upstreamJson for non-string payloads and uses a schema that requires code to be a string even though buildOpenAIErrorBody can emit code: null; update moderationErrorSchema to allow code: z.string().nullable() and ensure the response path (where upstreamJson is returned) instead always wraps any non-string upstreamJson into the standardized OpenAI error envelope via buildOpenAIErrorBody (or an equivalent normalization function) so all responses conform to { error: { message, type, param, code } }; adjust the other occurrence noted (lines ~654-663) to use the same normalization and nullable code schema.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/gateway/src/app.ts`:
- Around line 135-137: In renderGatewayError, remove the unsafe casts to any on
the status passed to c.json and instead import the type ContentfulStatusCode
from "hono/utils/http-status", then coerce once into a local const (e.g. const
jsonStatus = status as ContentfulStatusCode) and pass jsonStatus to both c.json
calls that use buildAnthropicErrorBody and buildOpenAIErrorBody; update the
imports at the top of the file to include the ContentfulStatusCode type and
delete the `as any` usages.
---
Outside diff comments:
In `@apps/gateway/src/moderations/moderations.ts`:
- Around line 133-140: The moderation handler currently returns raw upstreamJson
for non-string payloads and uses a schema that requires code to be a string even
though buildOpenAIErrorBody can emit code: null; update moderationErrorSchema to
allow code: z.string().nullable() and ensure the response path (where
upstreamJson is returned) instead always wraps any non-string upstreamJson into
the standardized OpenAI error envelope via buildOpenAIErrorBody (or an
equivalent normalization function) so all responses conform to { error: {
message, type, param, code } }; adjust the other occurrence noted (lines
~654-663) to use the same normalization and nullable code schema.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 533f34f2-dcce-4a58-a085-ae8f1eed9a40
📒 Files selected for processing (7)
apps/gateway/src/anthropic/anthropic.tsapps/gateway/src/api.spec.tsapps/gateway/src/app.tsapps/gateway/src/chat-custom-provider.e2e.tsapps/gateway/src/lib/error-response.spec.tsapps/gateway/src/lib/error-response.tsapps/gateway/src/moderations/moderations.ts
Temporarily include the deprecated top-level `message` and `status`
fields alongside the new OpenAI/Anthropic error envelopes so existing
consumers of the old `{error, status, message}` shape keep working
during migration. Both fields are marked deprecated and will be removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document the OpenAI-compatible error envelope returned across all endpoints, the status-code-to-type/code mapping, streaming error behavior, the Anthropic error shape for /v1/messages, and the deprecated legacy top-level message/status compat fields. Cross-link from the rate limits page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c3bacf5ed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (c.req.path.startsWith("/v1/messages")) { | ||
| return c.json(buildAnthropicErrorBody({ message, status }), status as any); | ||
| } | ||
| return c.json(buildOpenAIErrorBody({ message, status }), status as any); |
There was a problem hiding this comment.
Preserve Anthropic error types for streaming
When /v1/messages handles a streaming request, it forwards to /v1/chat/completions; gateway auth/limit failures from that internal call now come back from this line as an OpenAI envelope (type: invalid_request_error, code: invalid_api_key). The existing Anthropic streaming translator prefers error.type over error.code, so streamed Anthropic clients receive an invalid_request_error event instead of the authentication_error/billing_error shape that the non-streaming path now returns, making SDK error handling inconsistent for the same auth/credit failures. Please either return Anthropic-shaped errors for these internal calls or map the new OpenAI codes/statuses in the streaming translator.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/docs/content/resources/rate-limits.mdx (1)
61-69:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win429 JSON example should include
error.paramfor shape parity.The gateway’s OpenAI envelope includes
error.param(null when not applicable). This example omits it, which can mislead users about the exact response contract.Suggested doc patch
{ "error": { "message": "Rate limit exceeded. Try again later.", "type": "rate_limit_error", + "param": null, "code": "rate_limit_exceeded" } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/docs/content/resources/rate-limits.mdx` around lines 61 - 69, The JSON example for 429 responses in rate-limits.mdx is missing the error.param field required for parity with the gateway OpenAI envelope; update the example JSON inside the code block to include "param": null (or the appropriate value when applicable) under the "error" object so the shape matches the real response contract used by the API.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/docs/content/resources/error-handling.mdx`:
- Line 32: In the error.param table row update the description to fix the typo
by replacing "parameter-spec't." with a proper phrase such as
"parameter-specific"; specifically change the `error.param` description to read
something like: "The request parameter that caused the error, or `null` when not
parameter-specific." Ensure the backticks and punctuation remain consistent with
surrounding MDX.
- Around line 3-11: Update the doc text under "Error Handling" to avoid claiming
OpenAI-format errors apply universally; change phrases like "across all
endpoints" and "every error" to specify "all OpenAI-compatible endpoints" and
add a brief caveat that some endpoints (e.g., /v1/messages) use provider-native
formats such as Anthropic-native errors. Locate the paragraph that currently
states the OpenAI compatibility, replace the sweeping language with the narrowed
wording, and include a short parenthetical note referencing /v1/messages as an
exception.
- Around line 39-53: The table in error-handling.mdx omits the runtime mapping
for HTTP 499; update the table to include a row for 499 mapping to type
`invalid_request_error` and code `request_cancelled` to match the actual
behavior implemented in apps/gateway/src/lib/error-response.ts (where 499 is
mapped to `invalid_request_error` / `request_cancelled`), or if you prefer not
to assert exhaustiveness, change the table caption/text to “common statuses” to
indicate it’s not exhaustive.
---
Outside diff comments:
In `@apps/docs/content/resources/rate-limits.mdx`:
- Around line 61-69: The JSON example for 429 responses in rate-limits.mdx is
missing the error.param field required for parity with the gateway OpenAI
envelope; update the example JSON inside the code block to include "param": null
(or the appropriate value when applicable) under the "error" object so the shape
matches the real response contract used by the API.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 40f54e13-f9f8-44ef-98ce-55f382a75505
📒 Files selected for processing (3)
apps/docs/content/resources/error-handling.mdxapps/docs/content/resources/meta.jsonapps/docs/content/resources/rate-limits.mdx
✅ Files skipped from review due to trivial changes (1)
- apps/docs/content/resources/meta.json
- Replace `as any` status casts in renderGatewayError with Hono's ContentfulStatusCode. - Derive the Anthropic streaming error type from the HTTP status so streamed /v1/messages errors match the non-streaming path (previously the OpenAI envelope `type` leaked through the translator). - Docs: narrow OpenAI-format scope wording, note the /v1/messages exception, fix a typo, and add 499/504 rows to the status table. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
The missing-provider-key test asserted the legacy `{error, status,
message}` body via an inline snapshot. Update it to the OpenAI-compatible
envelope (with the temporary deprecated top-level message/status fields).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Problem
The Chat Completions endpoint (and the other OpenAI-compatible endpoints) is supposed to be OpenAI-compatible for everything, but gateway-level errors were not. Errors raised by the gateway itself — auth failure, usage/rate limit, validation, timeouts — came back as:
{"error":true,"status":401,"message":"Unauthorized: LLMGateway API key reached its usage limit."}instead of OpenAI's envelope:
{"error":{"message":"...","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}This breaks OpenAI SDK clients, which expect
errorto be an object withmessage/type/param/code. The format was also inconsistent internally: some endpoints (embeddings, images, parts of chat) already returned the OpenAI shape, while everything flowing through the global error handler did not.Fix
apps/gateway/src/lib/error-response.tswithbuildOpenAIErrorBody/buildAnthropicErrorBodyand status→type/code mappings.app.onErrorhandler now renders every error through these builders:{error: {message, type, param, code}}for all OpenAI-compatible endpoints (chat, embeddings, images, models, moderations, responses, videos).{type: "error", error: {type, message}}for/v1/messages(path-aware, so the Anthropic Messages API stays Anthropic-compatible).{error: true, ...}returns (Anthropic non-streaming passthrough, moderations fallback) to the same builders.Status → error mapping (OpenAI)
Tests
error-response.spec.tsunit tests for the builders/mappings.json.message→json.error.message(api.spec.ts, chat-custom-provider.e2e.ts).pnpm buildandpnpm formatpass; affected unit tests pass.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Refactor
Tests
Documentation