Skip to content

UN-3444 [FEAT] Propagate frontend request ID and surface it on error notifications#1955

Merged
chandrasekharan-zipstack merged 9 commits into
mainfrom
UN-3444-implement-and-propagate-request-id-from-frontend-to-backend
May 27, 2026
Merged

UN-3444 [FEAT] Propagate frontend request ID and surface it on error notifications#1955
chandrasekharan-zipstack merged 9 commits into
mainfrom
UN-3444-implement-and-propagate-request-id-from-frontend-to-backend

Conversation

@Deepak-Kesavan
Copy link
Copy Markdown
Contributor

@Deepak-Kesavan Deepak-Kesavan commented May 12, 2026

What

  • Add an axios request interceptor that injects X-Request-ID: <uuidv4> on every outgoing API call (both on the global axios default instance and on each useAxiosPrivate instance).
  • Extract the request ID from err.response.headers['x-request-id'] (with a fallback to err.config.headers['X-Request-ID'] for network/cancel errors) and thread it through useExceptionHandler and the alert store.
  • Render the request ID inline in the error notification toast with antd's Typography.Text copyable (one-click copy).
  • Append the request ID to the corresponding row in the bottom Logs & Notifications panel so it remains visible after the toast closes.

Why

  • Support and on-call needed a deterministic correlation ID to tie a UI error to backend/worker logs. Django and Flask middleware in the backend already honor an incoming X-Request-ID, but the frontend was neither sending one nor surfacing it to users.

How

  • New helper module frontend/src/helpers/requestId.js exposes attachRequestIdInterceptor(instance) and getRequestIdFromError(err). Interceptor only sets the header if one is not already on the request, so service-to-service hops (e.g. tool-sandbox already sends X-Request-ID) aren't overwritten.
  • App.jsx attaches the interceptor to the global axios import at module load; its returned handle is exported as globalRequestIdInterceptor so HMR-friendly cleanup is possible.
  • useAxiosPrivate.js registers + ejects the interceptor alongside the existing response interceptor.
  • useExceptionHandler.jsx adds requestId to every buildAlert return path (including the no-response branches).
  • App.jsx renders the request ID line in the notification description (copyable widget) and appends Request ID: \`to the log message thatpushLogMessageswrites — the table column already renders markdown viaCustomMarkdown`, which formats the UUID as an inline code pill.
  • alert-store.js extends the default alert shape with requestId.
  • index.css adds BEM-styled .notification-request-id for spacing/layout in the toast.

Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

  • No. The interceptor only sets the header when missing, so any existing flow that already sets X-Request-ID (e.g. tool-sandbox → runner) keeps its value. useExceptionHandler retains every prior return path; requestId is an additive optional field. The notification only renders the new line when an error alert has a request ID. The log message is augmented for NOTIFICATION rows only — LOG rows from socket messages are unchanged.

Database Migrations

  • None.

Env Config

  • None.

Relevant Docs

  • Backend middleware already wired (backend/middleware/request_id.py, backend/backend/settings/base.pyLOG_REQUEST_ID_HEADER, REQUEST_ID_RESPONSE_HEADER, GENERATE_REQUEST_ID_IF_NOT_IN_HEADER).
  • Flask middleware: unstract/core/src/unstract/core/flask/middleware.py:assign_request_id.

Related Issues or PRs

  • UN-3444

Dependencies Versions

  • Uses the existing uuid dep (already in frontend/package.json). No new dependencies.

Notes on Testing

  • Trigger any failing API call (e.g. invalid payload, expired auth, backend down) and verify:
    • The error toast shows Request ID: <uuid> with a copy icon that copies the raw UUID.
    • The bottom Logs panel row for the same error shows the request ID on a second line.
    • The Network tab's request headers include X-Request-ID and the value matches what is shown in the UI.
    • Backend logs (Django/Flask) for that request include the same UUID.
  • Offline path: disable network, retry — confirm the toast still shows the request ID (sourced from the outgoing request config).
  • Canceled request path: cancel an in-flight request — confirm the request ID is still surfaced.
  • Confirm non-error alerts (success/warning) do not render the request ID line.
  • Cross-origin caveat: if FE and BE are on different origins, reading x-request-id from the response requires CORS_EXPOSE_HEADERS = ["X-Request-ID"] on the backend; the fallback to err.config.headers keeps the feature working regardless because the backend reuses the header we sent.

Screenshots

image

Checklist

I have read and understood the Contribution Guidelines.

…notifications

- Add `X-Request-ID` request interceptor (uuidv4) on the global axios instance
  and on each `useAxiosPrivate` axios instance so every API call carries a
  client-generated correlation ID. Django/Flask backends already honor
  incoming `X-Request-ID`, so backend logs reuse the same value.
- Extract the request ID in `useExceptionHandler` from response headers with
  a fallback to the outgoing request headers (covers network/cancel errors).
- Thread `requestId` through the alert store and render it in the error
  notification with an antd `Typography.Text copyable` for a one-click copy.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds X-Request-ID support: header helpers and tests, module- and hook-level Axios interceptors, error extraction into exception handling, alert state updates, notification rendering with a copyable Request ID, and accompanying CSS.

Changes

Request ID Tracking

Layer / File(s) Summary
Request ID helpers and tests
frontend/src/helpers/requestId.js, frontend/src/helpers/requestId.test.js
Introduce REQUEST_ID_HEADER = "X-Request-ID", setHeaderIfMissing, attachRequestIdInterceptor(axiosInstance), getRequestIdFromError(err), and tests validating UUID injection, preservation, uniqueness, header creation, and extraction fallbacks.
App wiring and notification rendering
frontend/src/App.jsx, frontend/src/index.css
Guarded module-level attachRequestIdInterceptor(axios) to avoid double attachment, notification description now always renders content and conditionally shows a copyable Request ID for error alerts; socket log messages append the request ID when present; CSS adds .notification-request-id* styles.
Hook-level interceptor wiring
frontend/src/hooks/useAxiosPrivate.js
useAxiosPrivate attaches the request-id request interceptor to axiosPrivate and ensures the interceptor is ejected on cleanup alongside the response interceptor.
Exception handler: extract and include requestId
frontend/src/hooks/useExceptionHandler.jsx
Integrates getRequestIdFromError(err) and updates handleException to include requestId on all returned alert objects; replaces the old buildAlert helper with a closure that captures title/duration and attaches requestId.

Sequence Diagram

sequenceDiagram
  participant App
  participant Axios
  participant RequestInterceptor as attachRequestIdInterceptor
  participant Server
  participant ExceptionHandler as useExceptionHandler
  participant AlertStore as useAlertStore
  participant Notification

  App->>Axios: attachRequestIdInterceptor(axios)
  App->>Axios: send HTTP request
  Axios->>RequestInterceptor: outgoing request
  RequestInterceptor->>RequestInterceptor: ensure/generate X-Request-ID
  RequestInterceptor->>Server: request + X-Request-ID
  Server-->>Axios: response (error) + X-Request-ID header
  Axios->>ExceptionHandler: error thrown
  ExceptionHandler->>ExceptionHandler: getRequestIdFromError(err)
  ExceptionHandler->>AlertStore: setAlertDetails(..., requestId)
  AlertStore->>Notification: open with description + requestId
  Notification->>Notification: render content and Request ID block
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main feature: implementing request ID propagation from the frontend and displaying it in error notifications.
Description check ✅ Passed The PR description is comprehensive, covering all template sections with clear explanations of the what, why, and how, impact analysis, testing notes, and relevant context.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch UN-3444-implement-and-propagate-request-id-from-frontend-to-backend

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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR propagates a X-Request-ID UUID header on every outgoing axios request and surfaces the same ID in error notification toasts (with a one-click copy widget) and in the bottom Logs panel, giving support/on-call a deterministic correlation ID to tie UI errors to backend logs.

  • requestId.js: New helper adds a request interceptor (attachRequestIdInterceptor) that injects a uuidv4 header when absent, and getRequestIdFromError that reads the echoed response header with a fallback to the outgoing config header.
  • App.jsx: Uses Symbol.for to guard against duplicate registration on HMR, renders the ID in error toasts via Typography.Text copyable, and appends it as a markdown code span to the corresponding log row.
  • useAxiosPrivate.js / useExceptionHandler.jsx / alert-store.js: Interceptor registered/ejected symmetrically, requestId threaded through every handleException return path, and the store default extended with requestId: null.

Confidence Score: 5/5

Safe to merge — purely additive change that injects a new header and surfaces it in error UI without altering any existing request or error-handling behaviour.

The interceptor only sets the header when absent, the store default and exception handler changes are additive, and the HMR guard (Symbol.for flag) prevents duplicate interceptor accumulation. No existing flows are modified.

No files require special attention; the test helper in requestId.test.js exercises only the plain-object header path rather than the AxiosHeaders path that runs in production, but this does not affect runtime correctness.

Important Files Changed

Filename Overview
frontend/src/helpers/requestId.js New helper with interceptor registration and request-ID extraction; handles both AxiosHeaders and plain-object headers, uses nullish-coalescing for fallback chain.
frontend/src/helpers/requestId.test.js Good coverage of core paths, but all header-presence checks exercise the plain-object branch rather than the AxiosHeaders.set branch that runs in production.
frontend/src/App.jsx Symbol.for flag prevents duplicate interceptor registration on HMR; adds request-ID line to error toasts and log messages correctly gated on type === "error".
frontend/src/hooks/useAxiosPrivate.js Interceptor registered and ejected symmetrically in useEffect; correctly pairs with the existing response interceptor cleanup.
frontend/src/hooks/useExceptionHandler.jsx requestId threaded through all return paths via local closure; optional chaining in getRequestIdFromError makes the null-err path safe.
frontend/src/store/alert-store.js Additive null default for requestId; no behaviour change for existing non-error alerts.
frontend/src/index.css BEM-styled rules for toast request-ID display; word-break and flex layout look correct.

Sequence Diagram

sequenceDiagram
    participant UI as React UI
    participant Int as RequestId Interceptor
    participant BE as Backend (Django/Flask)
    participant Toast as Error Toast
    participant Logs as Logs Panel

    UI->>Int: axios request (no X-Request-ID)
    Int->>Int: inject X-Request-ID: uuidv4()
    Int->>BE: request with X-Request-ID header
    BE-->>Int: response (echoes X-Request-ID in header)
    Int-->>UI: error response
    UI->>UI: getRequestIdFromError(err) → response header ?? config header
    UI->>Toast: render Request ID with copyable widget
    UI->>Logs: append Request ID to log row
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
frontend/src/helpers/requestId.test.js:33-44
**Test helper always exercises the plain-object branch**

`runRequestInterceptors` spreads `config.headers` into a plain `{}`, so `setHeaderIfMissing` always takes the `!headers.set` path in every test. The production code path — where `config.headers` is an `AxiosHeaders` instance and `headers.set(name, value, false)` is called — is never exercised. If the `AxiosHeaders.set(…, false)` rewrite guard ever behaves differently than expected (e.g. a future axios change), these tests would still pass without catching it. Consider adding a test that passes an `AxiosHeaders` instance directly to confirm the `rewrite: false` guard prevents overwrites.

Reviews (8): Last reviewed commit: "Merge branch 'main' into UN-3444-impleme..." | Re-trigger Greptile

Comment thread frontend/src/helpers/requestId.js
Comment thread frontend/src/App.jsx Outdated
Comment thread frontend/src/helpers/requestId.js
- Use nullish coalescing in `getRequestIdFromError` so an empty-string
  backend-echoed header isn't silently shadowed by the request-side header.
- Drop the redundant mixed-case response-header lookup; browser/axios
  response headers are always lower-cased.
- Export the global axios interceptor ID so HMR-friendly cleanup is
  possible (and the chain doesn't leak across module re-evals).
@Deepak-Kesavan Deepak-Kesavan self-assigned this May 12, 2026
… panel

Notification rows in the Logs & Notifications table now include the
request ID on a second line (markdown inline-code) so users can reference
it without opening the toast.
Copy link
Copy Markdown
Contributor

@vishnuszipstack vishnuszipstack left a comment

Choose a reason for hiding this comment

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

PR Review Toolkit — automated multi-agent review

Ran Comment Analyzer, PR Test Analyzer, Silent Failure Hunter, Type Design Analyzer, Code Reviewer, and Code Simplifier against git diff main...HEAD. Overall the feature is well-structured and the helpers are cleanly extracted as pure functions. Findings below are posted inline, prioritized:

P1 (address before merge)

  • requestId.js:7 — interceptor uses bracket access on config.headers (an AxiosHeaders instance in axios 1.x) and assumes it is defined.

P2 (recommended)

  • requestId.js:5 — no tests added; the pure helpers are trivially testable (vitest is configured).
  • requestId.js:16 — response/config header-casing asymmetry is correct but fragile.
  • App.jsx:17 — module-level side effect at import time; interceptor never ejected; unused export.
  • useExceptionHandler.jsx:9buildAlert now takes 4 positional args; requestId default (undefined) diverges from alert-store.js (null); propTypes block is stale.

P3 (nice-to-have)

  • index.css:264font-size: 12px duplicated across all three rules.
  • App.jsx:62 — inline description JSX could be extracted; redundant optional chaining inside the showRequestId guard.

Note: the Comment Analyzer found no comment-accuracy issues (the PR adds no code comments).

Comment thread frontend/src/helpers/requestId.js Outdated
Comment thread frontend/src/helpers/requestId.js
Comment thread frontend/src/helpers/requestId.js
Comment thread frontend/src/App.jsx Outdated
Comment thread frontend/src/hooks/useExceptionHandler.jsx Outdated
Comment thread frontend/src/index.css Outdated
Comment thread frontend/src/App.jsx
- Use AxiosHeaders.set(name, value, false) when available so the
  case-insensitive normalization is respected and caller-supplied IDs
  are preserved; fall back to bracket access for plain-object headers.
- Guard against undefined config.headers on hand-built request configs.
- Collapse buildAlert into a local alert(content) closure inside
  handleException so each return site stops repeating title/duration/
  requestId. Default requestId to null to match the alert-store shape.
- Drop the stale, function-shaped useExceptionHandler.propTypes block.
- Make the global axios attach HMR-safe with a Symbol-based idempotency
  guard; remove the unused exported handle.
- Consolidate the .notification-request-id font-size rules and remove
  the now-empty __label class.
- Add vitest coverage for the helper (injection, no-overwrite, distinct
  IDs per request, fallback chain, null/empty inputs).
@vishnuszipstack
Copy link
Copy Markdown
Contributor

@Deepak-Kesavan approving. check the sonar issues.

- Drop the useless `|| {}` fallback when spreading config.headers.
- Use optional chaining `handler?.fulfilled` instead of `handler && handler.fulfilled`.
@sonarqubecloud
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown
Contributor

Frontend Lint Report (Biome)

All checks passed! No linting or formatting issues found.

@chandrasekharan-zipstack chandrasekharan-zipstack merged commit f97452e into main May 27, 2026
8 checks passed
@chandrasekharan-zipstack chandrasekharan-zipstack deleted the UN-3444-implement-and-propagate-request-id-from-frontend-to-backend branch May 27, 2026 12:20
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.

4 participants