Skip to content

Show management API request id in error notifications#698

Open
mlsmaycon wants to merge 2 commits into
mainfrom
feature/display-request-id-on-error
Open

Show management API request id in error notifications#698
mlsmaycon wants to merge 2 commits into
mainfrom
feature/display-request-id-on-error

Conversation

@mlsmaycon

@mlsmaycon mlsmaycon commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What

Newer management API servers set an X-Request-Id response header (telemetry.RequestIDHeader) on every response, including errors. This PR surfaces that request id in the dashboard's error notification so users can quote it in support tickets.

The id is shown only when the header is present, so older servers that don't send it are unaffected (the line simply isn't rendered).

Changes

  • src/utils/api.tsxErrorResponse gains an optional requestId; apiRequest reads X-Request-Id from the response headers and attaches it to both error-rejection paths (JSON-body error and the status-text fallback).
  • src/components/Notification.tsx — renders a copyable "Request ID: …" line whenever an error carries one. This covers every promise-based mutation toast through the existing .catch.
  • src/contexts/ErrorBoundary.tsx — threads the request id into the toast for GET/fetch errors.
  • e2e/tests/error-request-id.spec.ts — mocks /networks with a 500 in isolated browser contexts: header present → the id is shown; header absent → no Request ID line.

Screenshots

Fetch/GET error with request id

Fetch error with request id

Mutation error with request id

Mutation error with request id

Older server (no header) — no Request ID line

Error without request id

Notes

  • ⚠️ For the browser to read X-Request-Id cross-origin (e.g. app.netbird.ioapi.netbird.io), the management server must list it in Access-Control-Expose-Headers. If it isn't exposed, res.headers.get() returns null and the feature gracefully no-ops. Please confirm the header is exposed, or add it.
  • Verified with tsc --noEmit (clean). Note: npm run lint is currently broken on the base branch because Next.js 16 removed next lint and the repo has no flat ESLint config — unrelated to this change.

🤖 Generated with Claude Code

https://claude.ai/code/session_016B2yLBeDK3kvX2FvhqcWk8

Newer management servers return an X-Request-Id header on every response,
including errors. Surface it in the error toast so it can be quoted in
support tickets.

- api.tsx: read X-Request-Id from the response and attach it to the
  rejected ErrorResponse (both the JSON-body and status-text paths).
- Notification.tsx: render a copyable "Request ID" line whenever an error
  carries one, covering the promise-based mutation notifications.
- ErrorBoundary.tsx: thread the request id through for GET/fetch errors.

The line is only shown when the header is present, so older servers that
do not send it are unaffected. Adds an e2e spec covering both cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016B2yLBeDK3kvX2FvhqcWk8
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds propagation of a server X-Request-Id header through API error handling, ErrorBoundary, and the Notification component, which now displays and allows copying the request ID in error toasts. Includes a new Playwright e2e spec validating the toast behavior with and without the header.

Changes

Request ID propagation and display

Layer / File(s) Summary
API error response captures requestId
src/utils/api.tsx
ErrorResponse gains optional requestId; apiRequest reads the X-Request-Id header and attaches it to rejected errors in both failure paths.
ErrorBoundary forwards requestId
src/contexts/ErrorBoundary.tsx
Notify options now include requestId from the debounced error.
Notification UI displays and copies requestId
src/components/Notification.tsx
NotifyProps adds requestId; component tracks and updates requestId on rejection, adds a clipboard copy callback, and renders a conditional Request ID button with copy/check icon states.
E2E test for request-id toast
e2e/tests/error-request-id.spec.ts
New Playwright spec mocks /api/networks failures with and without X-Request-Id, asserting toast content and capturing screenshots.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Server
  participant apiRequest
  participant ErrorBoundaryProvider
  participant Notification
  participant User

  Server-->>apiRequest: 500 response + X-Request-Id header
  apiRequest->>apiRequest: parse error, extract requestId
  apiRequest-->>ErrorBoundaryProvider: reject(error + requestId)
  ErrorBoundaryProvider->>Notification: notify({..., requestId})
  Notification->>Notification: update requestId state
  Notification-->>User: render error toast with Request ID button
  User->>Notification: click copy button
  Notification->>Notification: copyRequestId() writes to clipboard
  Notification-->>User: show copied confirmation icon
Loading

Poem

A header hops in, small but bright,
"X-Request-Id" — copy it right! 🐇
From server to toast, the trail stays clear,
One click, it's copied, no need to fear.
Hoppy little bugs, now easier to trace,
This rabbit thumps twice — what a tidy case! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is detailed and on-topic, but it does not follow the template and is missing the issue ticket, documentation choice, and E2E test section. Add the issue ticket/link, select whether documentation was updated, and include the E2E tests section with any tag overrides or note that defaults are used.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: surfacing the management API request id in error notifications.
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.
✨ 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 feature/display-request-id-on-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.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016B2yLBeDK3kvX2FvhqcWk8

@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: 2

🧹 Nitpick comments (2)
src/components/Notification.tsx (2)

209-209: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Request ID button doesn't gate on error state.

requestId can be set via the initial requestIdProp regardless of whether the toast represents an error. Since NotifyProps.requestId is a public prop, a future non-error notify() call passing requestId would render the "Request ID:" line on a success toast, which could be confusing.

🔧 Suggested tightening
-            {!loading && requestId && (
+            {!loading && requestId && error && (
🤖 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 `@src/components/Notification.tsx` at line 209, The Request ID display in
Notification.tsx is gated only by loading and requestId, so a non-error toast
could still render it if requestId is provided. Update the conditional around
the Request ID block in the Notification component to also require the error
state, using the existing Notification/NotifyProps flow and the
requestIdProp-derived value so that only error toasts can show the “Request ID:”
line.

77-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider reusing the existing CopyToClipboardText component instead of hand-rolled clipboard logic.

The codebase already has a CopyToClipboardText component for this exact copy-to-clipboard + feedback pattern. Reimplementing navigator.clipboard.writeText + timed copied state here duplicates that logic.

Based on learnings, "In components that use CopyToClipboardText, the message prop should be the notification shown after copying, while the children content is what gets copied to the clipboard."

Also applies to: 209-230

🤖 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 `@src/components/Notification.tsx` around lines 77 - 86, The Notification copy
behavior is duplicating clipboard and copied-state logic in copyRequestId
instead of using the existing CopyToClipboardText component. Refactor the
request ID display in Notification.tsx to wrap the selectable ID with
CopyToClipboardText, passing the ID as the copied content and the copy-success
text through the message prop, and remove the manual
navigator.clipboard.writeText, setCopied, and timeout handling. Apply the same
pattern anywhere else in this component that reimplements the same copy flow,
using the existing CopyToClipboardText API as the source of truth.
🤖 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 `@e2e/tests/error-request-id.spec.ts`:
- Around line 61-63: The Playwright assertions in the error-request-id spec are
selecting status-code messages with getByText, which is brittle and violates the
test selector convention. Update the assertions in the relevant test block(s) to
use page.getByTestId() instead of page.getByText(), targeting the existing
data-testid on the status/error element(s) in this spec. Apply the same selector
change in the other referenced assertion block so all status-code checks use
stable test IDs.
- Around line 38-52: The test setup in openOwner() is bypassing the shared auth
fixture and creating a context directly, so update the spec to use the
dashboardAsOwner fixture instead of manual browser.newContext/storageState/login
flow. Refactor the test in error-request-id.spec.ts to rely on the custom
fixture from helpers/fixtures.ts and keep the page/close handling aligned with
that fixture pattern, including any other affected call sites in this spec.

---

Nitpick comments:
In `@src/components/Notification.tsx`:
- Line 209: The Request ID display in Notification.tsx is gated only by loading
and requestId, so a non-error toast could still render it if requestId is
provided. Update the conditional around the Request ID block in the Notification
component to also require the error state, using the existing
Notification/NotifyProps flow and the requestIdProp-derived value so that only
error toasts can show the “Request ID:” line.
- Around line 77-86: The Notification copy behavior is duplicating clipboard and
copied-state logic in copyRequestId instead of using the existing
CopyToClipboardText component. Refactor the request ID display in
Notification.tsx to wrap the selectable ID with CopyToClipboardText, passing the
ID as the copied content and the copy-success text through the message prop, and
remove the manual navigator.clipboard.writeText, setCopied, and timeout
handling. Apply the same pattern anywhere else in this component that
reimplements the same copy flow, using the existing CopyToClipboardText API as
the source of truth.
🪄 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: 0f8da750-f41d-472b-963b-184c45a15185

📥 Commits

Reviewing files that changed from the base of the PR and between 30dd75d and 127a3d4.

📒 Files selected for processing (4)
  • e2e/tests/error-request-id.spec.ts
  • src/components/Notification.tsx
  • src/contexts/ErrorBoundary.tsx
  • src/utils/api.tsx

Comment on lines +38 to +52
async function openOwner(
browser: Browser,
): Promise<{ page: Page; close: () => Promise<void> }> {
const context = await browser.newContext({
storageState: "e2e/fixtures/auth/owner.json",
});
const page = await context.newPage();
return { page, close: () => context.close() };
}

test.describe.serial("API error request id @error-handling", () => {
test("shows the X-Request-Id from a failed request in the error toast", async ({
browser,
}) => {
const { page, close } = await openOwner(browser);

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use dashboardAsOwner fixture instead of manually creating contexts/login.

openOwner() manually spins up a browser.newContext() with storageState and calls loginToApp, bypassing the project's custom auth fixtures.

As per path instructions, "Use custom fixtures (dashboardAsOwner or dashboardAsUser) from helpers/fixtures.ts instead of raw page for test authentication."

Also applies to: 77-80

🤖 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 `@e2e/tests/error-request-id.spec.ts` around lines 38 - 52, The test setup in
openOwner() is bypassing the shared auth fixture and creating a context
directly, so update the spec to use the dashboardAsOwner fixture instead of
manual browser.newContext/storageState/login flow. Refactor the test in
error-request-id.spec.ts to rely on the custom fixture from helpers/fixtures.ts
and keep the page/close handling aligned with that fixture pattern, including
any other affected call sites in this spec.

Comment on lines +61 to +63
await expect(
page.getByText("Request failed with status code 500").first(),
).toBeVisible();

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use data-testid selectors instead of getByText for these assertions.

The status-code text assertions use page.getByText(...) rather than a data-testid selector, and are also brittle to copy changes.

As per path instructions, "Always use data-testid selectors via page.getByTestId() for element selection in Playwright tests."

Also applies to: 87-89

🤖 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 `@e2e/tests/error-request-id.spec.ts` around lines 61 - 63, The Playwright
assertions in the error-request-id spec are selecting status-code messages with
getByText, which is brittle and violates the test selector convention. Update
the assertions in the relevant test block(s) to use page.getByTestId() instead
of page.getByText(), targeting the existing data-testid on the status/error
element(s) in this spec. Apply the same selector change in the other referenced
assertion block so all status-code checks use stable test IDs.

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.

1 participant