Skip to content

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

Open
Deepak-Kesavan wants to merge 8 commits into
mainfrom
UN-3444-implement-and-propagate-request-id-from-frontend-to-backend
Open

UN-3444 [FEAT] Propagate frontend request ID and surface it on error notifications#1955
Deepak-Kesavan wants to merge 8 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 adds end-to-end correlation ID support by injecting a X-Request-ID UUID on every outgoing axios request and surfacing it in error notifications and the Logs panel so support teams can tie UI errors to backend logs.

  • requestId.js introduces attachRequestIdInterceptor (sets the header once per request, never overwrites a caller-supplied value) and getRequestIdFromError (reads the lowercased response header, falling back to the outgoing config header for network/cancel errors).
  • App.jsx guards against HMR double-registration with a Symbol.for flag; error toasts render a copyable UUID widget and the log row is augmented with a markdown-formatted request ID.
  • useExceptionHandler.jsx is cleanly refactored to a single alert factory that closes over requestId, threading it through every error return path.

Confidence Score: 5/5

Safe to merge — the change is purely additive, every prior error path is preserved, and no existing header values are overwritten.

The interceptor only mutates requests that do not already carry X-Request-ID, the Symbol.for guard prevents duplicate registration on hot-reload, and requestId is an opt-in field that non-error alerts simply ignore. No regressions to existing flows are expected.

No files require special attention. The test file accesses interceptors.request.handlers (an internal axios property) but this pattern is stable across current axios versions used in the project.

Important Files Changed

Filename Overview
frontend/src/helpers/requestId.js New helper with attachRequestIdInterceptor and getRequestIdFromError; correctly uses ?? for the fallback chain and lowercased response-header key.
frontend/src/helpers/requestId.test.js Good test coverage for both helpers; runRequestInterceptors accesses interceptors.request.handlers, an internal axios API that could silently break on a major axios upgrade.
frontend/src/App.jsx Uses Symbol.for flag to prevent duplicate global-interceptor registration on HMR; renders copyable request-ID widget in error toasts and appends it to log messages.
frontend/src/hooks/useAxiosPrivate.js Correctly registers the request-ID interceptor alongside the response interceptor and ejects both in the cleanup function.
frontend/src/hooks/useExceptionHandler.jsx Refactored to a single inner alert factory that closes over requestId; every return path now carries the request ID without duplicating fields.
frontend/src/store/alert-store.js Adds requestId: null to the default alert shape, keeping the store backward-compatible.
frontend/src/index.css Adds BEM-scoped CSS for the new request-ID row; word-break: break-all on the UUID value is appropriate.

Sequence Diagram

sequenceDiagram
    participant Browser as Browser (App.jsx)
    participant Interceptor as Request Interceptor (requestId.js)
    participant Backend as Backend API
    participant Handler as useExceptionHandler
    participant Store as alert-store
    participant Toast as Notification Toast
    participant Logs as Logs Panel

    Browser->>Interceptor: outgoing axios request
    Interceptor->>Interceptor: X-Request-ID absent?
    Interceptor-->>Browser: inject X-Request-ID: uuidv4()
    Browser->>Backend: HTTP request with X-Request-ID
    alt Success
        Backend-->>Browser: 2xx response
    else Error
        Backend-->>Browser: error response with x-request-id
        Browser->>Handler: handleException(err)
        Handler->>Handler: getRequestIdFromError(err) response header ?? config header
        Handler->>Store: type error with requestId
        Store->>Toast: alertDetails with requestId
        Toast->>Toast: render copyable UUID widget
        Toast->>Logs: pushLogMessages with Request ID line
    end
Loading

Reviews (7): 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`.
@github-actions
Copy link
Copy Markdown
Contributor

Frontend Lint Report (Biome)

All checks passed! No linting or formatting issues found.

@sonarqubecloud
Copy link
Copy Markdown

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.

3 participants