Skip to content

feat: improve error messages for unclear HTTP error responses#443

Open
LiteSun wants to merge 4 commits into
mainfrom
fix/improve-timeout-error-message
Open

feat: improve error messages for unclear HTTP error responses#443
LiteSun wants to merge 4 commits into
mainfrom
fix/improve-timeout-error-message

Conversation

@LiteSun
Copy link
Copy Markdown
Contributor

@LiteSun LiteSun commented May 12, 2026

Summary

Fixes #442

When the API7 Dashboard returns non-2xx HTTP responses with empty body (e.g., HTTP 500 under high concurrency), ADC displayed Error: "" which provides no useful diagnostic information.

Root Cause Analysis

The bug is in the catchError handler in operator.ts across all three backends:

new Error(
  error.response?.data?.error_msg ??
    JSON.stringify(error.response?.data),
)

When the Dashboard returns HTTP 500 with empty body:

  • error.response.data = "" (empty string)
  • "".error_msg = undefined
  • JSON.stringify("") = '""' (the string "")
  • Result: Error: ""

This happens because:

  1. The Dashboard can return 500 + empty body under high concurrency (verified via real API calls)
  2. The ?? (nullish coalescing) only checks null/undefined, not empty string
  3. JSON.stringify("") produces the misleading "" output

Why 499 appears in Dashboard logs

When ADC sync runs with high concurrency (e.g., --request-concurrent 1000) and one request fails:

  1. exitOnFailure: true causes throwError() in the rxjs chain
  2. mergeMap tears down all inner observables
  3. Remaining ~999 in-flight HTTP connections are closed
  4. Dashboard Nginx logs each client disconnect as 499

The 499s are a side effect of the first error, not the cause.

Changes

1. formatAxiosErrorMessage() in SDK utils (libs/sdk/src/utils.ts)

New shared function that formats axios error responses with:

  • HTTP method and URL
  • Response status code and status text
  • error_msg from response body (if available)
  • Raw response body (when no error_msg field exists)

Before: Error: ""
After: Error: PUT https://dashboard.example.com/apisix/admin/routes/123, responded with status 500 Internal Server Error

2. Applied to all three backends

Updated catchError in operator.ts for:

  • libs/backend-api7/src/operator.ts
  • libs/backend-apisix/src/operator.ts
  • libs/backend-apisix-standalone/src/operator.ts

Both exitOnFailure and non-exitOnFailure paths are fixed.

3. Timeout error improvement (previous commits)

Added registerTimeoutInterceptor that enhances timeout errors:

Before: AxiosError: timeout of 5000ms exceeded (no URL/method info)
After: Request "GET https://example.com/api/version" timed out after 5000ms. Consider increasing the timeout with the --timeout flag.

Tests

  • 6 new unit tests for formatAxiosErrorMessage covering:
    • Normal error with error_msg
    • Empty body response (the Error: "" scenario)
    • HTML response body (e.g., Nginx 502)
    • Absolute URL handling
    • Missing config graceful handling
    • JSON response without error_msg
  • 5 existing unit tests for registerTimeoutInterceptor
  • 4 existing integration tests for timeout scenarios

Test coverage note: The Error: "" scenario requires the Dashboard to return HTTP 500 with empty body, which occurs under specific high-concurrency conditions on the same resource. This was verified through real API calls (300 concurrent PUTs to the same route ID produced 63% HTTP 500 + empty body responses). The fix is defensive and handles all response body formats correctly.

Summary by CodeRabbit

Release Notes

  • Improvements

    • Timeout errors now include request method, URL, and timeout duration to aid debugging and troubleshooting
    • Enhanced error message formatting and consistency across all API operations
  • Tests

    • Added timeout error handling tests to validate error message accuracy and content

Review Change Stack

When a timeout error occurs, the error message now includes the HTTP
method, URL, timeout duration, and a hint to use the --timeout flag.
This applies to all backends (API7, APISIX, APISIX Standalone).

Before: AxiosError: timeout of 10000ms exceeded
After:  Request "GET https://example.com/api/version" timed out after
        10000ms. Consider increasing the timeout with the --timeout flag.

Closes #442

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@LiteSun LiteSun requested a review from bzp2010 as a code owner May 12, 2026 08:52
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c21f7f64-6255-43c4-bdda-b5ac75c0a5de

📥 Commits

Reviewing files that changed from the base of the PR and between e472ecf and e015bb3.

📒 Files selected for processing (5)
  • libs/backend-api7/src/operator.ts
  • libs/backend-apisix-standalone/src/operator.ts
  • libs/backend-apisix/src/operator.ts
  • libs/sdk/src/utils.spec.ts
  • libs/sdk/src/utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/sdk/src/utils.spec.ts

📝 Walkthrough

Walkthrough

Adds an SDK helper registerTimeoutInterceptor and formatAxiosErrorMessage, tests timeout message rewriting and non-timeout passthrough, registers the interceptor in BackendAPI7, BackendAPISIXStandalone, and BackendAPISIX constructors, and updates operator sync paths to use the new formatter.

Changes

Timeout Error Message Enhancement

Layer / File(s) Summary
Timeout error handler utility
libs/sdk/src/utils.ts
Adds formatAxiosErrorMessage to build a compact Axios error message and registerTimeoutInterceptor(client) which rewrites Axios ECONNABORTED timeout errors' error.message (method + resolved URL + timeout or "unknown duration"), optionally updates the first stack line, and rethrows. Exports updated to include both functions.
SDK interceptor tests
libs/sdk/src/utils.spec.ts
Adds tests for registerTimeoutInterceptor: verifies enriched timeout message and stack update, absolute URL handling without duplicating baseURL, missing timeout duration handling, and that non-timeout Axios errors are unchanged.
Backend client integration & BackendAPI7 tests
libs/backend-api7/src/index.ts, libs/backend-apisix-standalone/src/index.ts, libs/backend-apisix/src/index.ts, libs/backend-api7/test/timeout.spec.ts
Each backend client constructor now calls ADCSDK.utils.registerTimeoutInterceptor(this.client). BackendAPI7 gains a test suite asserting timeout errors for ping, version, dump, and sync include the request URL and timeout hint/duration.
Operator sync error formatting
libs/backend-api7/src/operator.ts, libs/backend-apisix-standalone/src/operator.ts, libs/backend-apisix/src/operator.ts
Operator.sync implementations now use ADCSDK.utils.formatAxiosErrorMessage(error) to construct thrown or returned Error messages when Axios errors occur, and gate non-exitOnFailure returned errors on error.response.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • juzhiyuan
  • AlinsRan
  • guoqqqi
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning E2E timeout integration tests only exist for backend-api7, not for apisix/apisix-standalone. No E2E tests for formatAxiosErrorMessage usage in operator sync() error paths. Add timeout E2E tests for apisix backends. Add E2E tests verifying formatAxiosErrorMessage in sync() error handling paths.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main objective of improving error messages for HTTP error responses, which is the core focus of the changeset.
Linked Issues check ✅ Passed The PR fully addresses issue #442 by implementing registerTimeoutInterceptor for clearer timeout messages and formatAxiosErrorMessage for improved HTTP error clarity across all three backends.
Out of Scope Changes check ✅ Passed All changes are directly related to improving error messages (timeout interceptors and error formatting) and their application across backends and tests; no unrelated modifications detected.
Security Check ✅ Passed No critical security vulnerabilities found. Response body serialization in error messages is intentional per PR design, with proper safeguards (no credential capture, tokens in headers only).

✏️ 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/improve-timeout-error-message

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

@LiteSun LiteSun marked this pull request as draft May 12, 2026 08:53
Add integration-level tests using a local HTTP server with delayed
responses to verify that timeout errors include clear, actionable
messages across all BackendAPI7 operations: ping, version, dump, sync.

Also enhance SDK unit test to verify stack trace is updated alongside
the error message.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 `@libs/sdk/src/utils.ts`:
- Around line 35-38: The rewritten timeout message builds an invalid endpoint
when error.config.url is absolute and shows "undefinedms" when timeout is
missing; update the logic that constructs url and newMessage: derive url by
using error.config.url unmodified if it looks like an absolute URL (e.g., starts
with "http://" "https://" or "//"), otherwise join baseURL and url with a single
separator to avoid double slashes, and set timeout to a safe fallback (e.g.,
"unknown" or "not set") before interpolating so newMessage never contains
"undefinedms". Reference error.config, method, url, timeout and newMessage when
making this defensive change.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa42f622-64a3-4df8-82da-18ce7fdab8fd

📥 Commits

Reviewing files that changed from the base of the PR and between bcf6199 and 798864f.

📒 Files selected for processing (5)
  • libs/backend-api7/src/index.ts
  • libs/backend-apisix-standalone/src/index.ts
  • libs/backend-apisix/src/index.ts
  • libs/sdk/src/utils.spec.ts
  • libs/sdk/src/utils.ts

Comment thread libs/sdk/src/utils.ts Outdated
Address CodeRabbit review feedback:
- Detect absolute URLs (http(s)://) and avoid duplicating baseURL
- Handle missing timeout value gracefully (show 'an unknown duration'
  instead of 'undefinedms')
- Add test cases for both edge cases

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

@LiteSun LiteSun marked this pull request as ready for review May 12, 2026 09:09
@LiteSun LiteSun marked this pull request as draft May 12, 2026 09:10
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
libs/backend-api7/test/timeout.spec.ts (1)

17-21: ⚡ Quick win

Reduce artificial delay to keep this suite fast.

Using a 5s delayed response can keep timers alive long after each timeout assertion already passed, which slows CI unnecessarily. A much smaller delay (still >10ms) is enough for the same behavior.

Proposed change
 describe('BackendAPI7 timeout error message', () => {
+  const RESPONSE_DELAY_MS = 200;
   // Create a local HTTP server that delays responses to trigger timeouts
   let server: ReturnType<typeof createServer>;
@@
     server = createServer((_, res) => {
-      // Delay response by 5 seconds to guarantee timeout
-      setTimeout(() => {
+      // Delay response to guarantee timeout
+      const timer = setTimeout(() => {
         res.writeHead(200, { 'Content-Type': 'application/json' });
         res.end(JSON.stringify({ value: {} }));
-      }, 5000);
+      }, RESPONSE_DELAY_MS);
+      timer.unref();
     });
🤖 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 `@libs/backend-api7/test/timeout.spec.ts` around lines 17 - 21, The test's
artificial response delay in timeout.spec.ts uses setTimeout with 5000ms which
slows CI; change the delay in the anonymous response callback (the setTimeout in
the test server handler) to a much smaller value that is still greater than the
client timeout threshold (e.g., 20ms or similar >10ms) so the test behavior
remains the same but the suite runs faster and timers don't linger.
🤖 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.

Nitpick comments:
In `@libs/backend-api7/test/timeout.spec.ts`:
- Around line 17-21: The test's artificial response delay in timeout.spec.ts
uses setTimeout with 5000ms which slows CI; change the delay in the anonymous
response callback (the setTimeout in the test server handler) to a much smaller
value that is still greater than the client timeout threshold (e.g., 20ms or
similar >10ms) so the test behavior remains the same but the suite runs faster
and timers don't linger.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c7723d4a-e9cd-4d93-b7f7-0d52b045cb72

📥 Commits

Reviewing files that changed from the base of the PR and between 798864f and e472ecf.

📒 Files selected for processing (3)
  • libs/backend-api7/test/timeout.spec.ts
  • libs/sdk/src/utils.spec.ts
  • libs/sdk/src/utils.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/sdk/src/utils.spec.ts
  • libs/sdk/src/utils.ts

When the API7 Dashboard returns non-2xx responses with empty body
(e.g., HTTP 500 under high concurrency), the error message was
displayed as 'Error: ""' which provides no useful diagnostic info.

The root cause is that JSON.stringify('') produces '""', and when
error.response.data is an empty string, error_msg is undefined,
falling through to JSON.stringify.

This commit adds formatAxiosErrorMessage() to the SDK utils that
always includes HTTP method, URL, status code, and status text in
error messages. When error_msg is available, it is included; when
the response body is empty or lacks error_msg, the status code and
URL still provide useful diagnostic context.

Applied to all three backends: api7, apisix, apisix-standalone.

Closes #442

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@LiteSun LiteSun changed the title fix: improve timeout error message with request details feat: improve error messages for unclear HTTP error responses May 13, 2026
@LiteSun LiteSun marked this pull request as ready for review May 13, 2026 07:49
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.

When a timeout error occurs, the error message is not clear enough.

1 participant