Skip to content

fix(gateway): don't log forwarded upstream errors as errors - #2548

Closed
steebchen wants to merge 1 commit into
mainfrom
fix/upstream-error-not-logged-as-error
Closed

steebchen wants to merge 1 commit into
mainfrom
fix/upstream-error-not-logged-as-error

Conversation

@steebchen

@steebchen steebchen commented Jun 5, 2026 •

Copy link
Copy Markdown
Member

Problem

A google-vertex 429 Too Many Requests (an expected, transient upstream rate limit) was being surfaced in logs at ERROR severity, tripping error alerting:

ERROR {"err":{"message":"Error from provider google-vertex: 429 Too Many Requests {...RESOURCE_EXHAUSTED...}","stack":"Error: Error from provider google-vertex: 429 ..."}}

These are expected upstream errors and should not be reported as logger errors — they just need to be marked as upstream_error / gateway_error in the log table (which already happens).

Root cause

The "Error from provider ..." string is only ever built as a response body in chat.ts (returned via c.json/SSE, never thrown). The image-generation endpoint forwards to /v1/chat/completions (images.ts:forwardToChatCompletions); on an upstream error chat.ts wraps it as HTTP 500, and images.ts re-threw it as HTTPException(500). The global handler app.onError logs all HTTPException with status ≥ 500 at ERROR (app.ts:170), so the forwarded provider 429 became an ERROR log.

The sibling forwarders (responses.ts, anthropic.ts) return the upstream error via c.json and never hit this path — images.ts was the outlier.

Fix

  • images.ts now tags forwarded upstream/gateway provider errors (error.type/code of upstream_error or gateway_error) via the HTTPException cause.
  • app.onError recognizes that marker and logs at warn ("Provider error", with the unifiedFinishReason) instead of ERROR, while still returning the same error response to the client.
  • New upstreamErrorCause / getUpstreamErrorCause helpers in error-response.ts, with unit tests.

No DB change needed: the internal chat-completions call already inserts the log row with the correct unifiedFinishReason (429 → upstream_error). The client-facing response is unchanged.

Testing

  • pnpm build — full monorepo build passes (17/17).
  • pnpm exec vitest run apps/gateway/src/lib/error-response.spec.ts — 6/6 pass.
  • pnpm format clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved error logging and message extraction for upstream provider failures
    • Enhanced error cause tracking to distinguish provider errors from internal issues
  • Tests

    • Added test coverage for upstream error detection and handling utilities
  • Chores

    • Refined error response handling and categorization system

When image generation forwards to chat completions and the provider
returns an upstream error (e.g. google-vertex 429), images.ts re-threw
it as an HTTPException(500), which app.onError logged at ERROR severity
and tripped error alerting.

Mark these forwarded upstream/gateway provider errors via the exception
cause so the global handler surfaces them as warnings. They are already
recorded in the log table with the correct unifiedFinishReason by the
internal chat-completions call, so no DB classification change is needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 5, 2026 13:15
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds upstream error cause detection to distinguish provider failures from internal errors. It introduces a new UpstreamErrorCause marker interface and type guards in the shared error-response library, then integrates them into the main gateway error handler and the images forwarding module to log upstream errors with provider-specific metadata instead of generic HTTP status codes.

Changes

Upstream Error Cause Detection

Layer / File(s) Summary
Upstream error cause marker contract and tests
apps/gateway/src/lib/error-response.ts, apps/gateway/src/lib/error-response.spec.ts
Defines UpstreamErrorCause interface containing upstreamError: true and a unifiedFinishReason string. Exports upstreamErrorCause() constructor and getUpstreamErrorCause() type guard for detecting and extracting the marker. Tests verify construction, extraction, and null handling for non-marker inputs.
Gateway error handler integration
apps/gateway/src/app.ts
Imports getUpstreamErrorCause and extends the HTTPException error handler to extract upstream cause; when present, logs "Provider error" warning with status, message, and unifiedFinishReason instead of generic 5xx/4xx paths. Response behavior remains unchanged.
Images module error handling
apps/gateway/src/images/images.ts
Imports upstreamErrorCause and ContentfulStatusCode to improve type safety. Updates forwardToChatCompletions error path to parse upstream response bodies, extract error messages, detect upstream_error/gateway_error types, and attach them as HTTPException causes.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • theopenco/llmgateway#2532: Both PRs modify the gateway's centralized error handling via apps/gateway/src/app.ts and apps/gateway/src/lib/error-response utilities—this PR introduces UpstreamErrorCause markers and their consumption, while that PR refactors error envelope rendering through the same helpers.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: preventing upstream provider errors from being logged at ERROR severity. It directly aligns with the PR's primary objective of fixing logging behavior for forwarded upstream errors.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/upstream-error-not-logged-as-error

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces noisy ERROR-level logging for expected upstream/provider failures (e.g., provider rate limits) when the images endpoint internally forwards to chat-completions and surfaces those failures via HTTPException.

Changes:

  • Introduces an HTTPException.cause marker (upstreamErrorCause / getUpstreamErrorCause) to identify forwarded upstream/gateway provider errors.
  • Updates the images forwarder to attach the marker when it detects upstream_error / gateway_error in the internal chat-completions error payload.
  • Adjusts the global app.onError handler to log these marked errors at WARN instead of ERROR, while preserving the client response behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
apps/gateway/src/lib/error-response.ts Adds helpers for creating/reading a marker used to classify forwarded upstream/gateway provider errors.
apps/gateway/src/lib/error-response.spec.ts Adds unit tests covering marker creation and detection behavior.
apps/gateway/src/images/images.ts Attaches the marker as HTTPException cause when forwarding internal chat-completions upstream/gateway provider errors.
apps/gateway/src/app.ts Detects the marker in onError and downgrades log severity to WARN for these expected provider failures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +150 to +159
if (
cause &&
typeof cause === "object" &&
(cause as Partial<UpstreamErrorCause>).upstreamError === true &&
typeof (cause as Partial<UpstreamErrorCause>).unifiedFinishReason ===
"string"
) {
return cause as UpstreamErrorCause;
}
return null;

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/images/images.ts`:
- Around line 347-350: The code derives errorType using parsed?.error?.type ??
parsed?.error?.code which ignores parsed.error.code when parsed.error.type
exists but is a non-marker; update the logic around the errorType assignment
(the block that sets upstreamFinishReason) to check parsed?.error?.type and
parsed?.error?.code independently and set upstreamFinishReason if either equals
"upstream_error" or "gateway_error" (do the same fix for the similar logic later
in the file that mirrors this behavior). Ensure you reference parsed.error.type
and parsed.error.code separately when deciding to assign upstreamFinishReason.
🪄 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: 80404366-b412-4393-b3ab-a2064314ae90

📥 Commits

Reviewing files that changed from the base of the PR and between 3b834c3 and 855d388.

📒 Files selected for processing (4)
  • apps/gateway/src/app.ts
  • apps/gateway/src/images/images.ts
  • apps/gateway/src/lib/error-response.spec.ts
  • apps/gateway/src/lib/error-response.ts

Comment on lines +347 to +350
const errorType = parsed?.error?.type ?? parsed?.error?.code;
if (errorType === "upstream_error" || errorType === "gateway_error") {
upstreamFinishReason = errorType;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Check both error.type and error.code independently when deriving upstream markers.

Line 347 currently uses parsed?.error?.type ?? parsed?.error?.code, so if error.type exists but is non-marker (e.g. "api_error"), a marker in error.code is ignored. That drops cause, and Line 181 in apps/gateway/src/app.ts falls back to 5xx ERROR logging.

Suggested fix
-			const errorType = parsed?.error?.type ?? parsed?.error?.code;
-			if (errorType === "upstream_error" || errorType === "gateway_error") {
-				upstreamFinishReason = errorType;
-			}
+			const errorType = parsed?.error?.type;
+			const errorCode = parsed?.error?.code;
+			if (errorType === "upstream_error" || errorType === "gateway_error") {
+				upstreamFinishReason = errorType;
+			} else if (
+				errorCode === "upstream_error" ||
+				errorCode === "gateway_error"
+			) {
+				upstreamFinishReason = errorCode;
+			}

Also applies to: 357-359

🤖 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/images/images.ts` around lines 347 - 350, The code derives
errorType using parsed?.error?.type ?? parsed?.error?.code which ignores
parsed.error.code when parsed.error.type exists but is a non-marker; update the
logic around the errorType assignment (the block that sets upstreamFinishReason)
to check parsed?.error?.type and parsed?.error?.code independently and set
upstreamFinishReason if either equals "upstream_error" or "gateway_error" (do
the same fix for the similar logic later in the file that mirrors this
behavior). Ensure you reference parsed.error.type and parsed.error.code
separately when deciding to assign upstreamFinishReason.

@steebchen

Copy link
Copy Markdown
Member Author

Closing as superseded. Main has since fixed the same issue more broadly:

Rebasing this branch conflicts in both app.ts and images.ts, and the cause-marker approach here covers a strict subset of what main already handles (it only tags upstream_error/gateway_error, while main also catches error types and the Error from provider message prefix).

@steebchen steebchen closed this Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants