Skip to content

Fix chatbox trace#1867

Merged
chelojimenez merged 3 commits intomainfrom
chatbox-session-trace-views
Apr 19, 2026
Merged

Fix chatbox trace#1867
chelojimenez merged 3 commits intomainfrom
chatbox-session-trace-views

Conversation

@chelojimenez
Copy link
Copy Markdown
Contributor

@chelojimenez chelojimenez commented Apr 19, 2026

Note

Medium Risk
Moderate risk: changes the shared-thread viewer to fetch and render trace span blobs and adjusts server ingestion to forward selected request headers, which could affect trace display correctness and header/privacy handling if misconfigured.

Overview
Fixes shared chatbox trace viewing by adding a Chat / Trace / Raw toggle and wiring ShareUsageThreadDetail to load per-turn trace metadata, fetch span blobs, and render them via TraceViewer with computed start/end timing.

Improves usage-insights enrichment during chat ingestion by introducing pickEnrichmentHeaders and forwarding a curated set of browser/edge headers (UA/language/geo/IP) from both mcp and web chat-v2 routes into the Convex /ingest-chat request.

Reviewed by Cursor Bugbot for commit 5198567. Bugbot is set up for automated code reviews on this repo. Configure here.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Apr 19, 2026
@chelojimenez
Copy link
Copy Markdown
Contributor Author

chelojimenez commented Apr 19, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@dosubot dosubot bot added the bug Something isn't working label Apr 19, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 19, 2026

Internal preview

Preview URL will appear in Railway after the deploy finishes.
Deployed commit: 799369d
PR head commit: 5198567
Backend target: staging fallback.
Access is employee-only in non-production environments.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 19, 2026

Walkthrough

The changes implement turn-trace hydration and visualization on the client side, coupled with enrichment header forwarding on the server side. A new SharedChatTurnTrace interface and useSharedChatTurnTraces hook enable fetching per-turn trace metadata. The ShareUsageThreadDetail component now hydrates span data from each turn's blob URL and toggles between chat and trace viewing modes, rendering traces via a TraceViewer component with constructed timing data. Simultaneously, server routes now extract enrichment-relevant headers (device, language, geo, IP-related) and forward them to the Convex ingestion endpoint via the chat persistence layer.


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.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1eda59d. Configure here.

headers: {
"content-type": "application/json",
authorization: options.authHeader,
...options.forwardHeaders,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forwarded headers can override authorization header

Medium Severity

forwardHeaders is spread after the authorization and content-type headers in the fetch call to the Convex /ingest-chat endpoint. Because forwardHeaders originates from client request headers (via pickEnrichmentHeaders), any key collision would silently override the server-side authorization value. While the current whitelist in pickEnrichmentHeaders doesn't include those keys, the spread order is a latent vulnerability — a future whitelist change or alternate call-site could allow a client to hijack the server's auth to Convex. Spreading forwardHeaders before the critical headers would eliminate this risk.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1eda59d. Configure here.

Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mcpjam-inspector/client/src/components/connection/share-usage/ShareUsageThreadDetail.tsx (1)

186-217: ⚠️ Potential issue | 🟡 Minor

Loading gate ignores trace hydration — switching to Timeline/Raw early yields an empty viewer.

The early-return isLoadingMessages check doesn't wait for turnTraces (Convex query) or hydratedSpans to resolve. If a user clicks the Timeline or Raw tab before span blobs finish downloading, TraceViewer receives spans: [] and renders as though the trace has none, then silently fills in once hydration completes. Consider gating the trace modes on a hydration-pending flag, or showing a lightweight spinner inside the trace panel while turnTraces === undefined or spans are still loading — small touch, but it prevents a "where did my trace go?" moment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@mcpjam-inspector/client/src/components/connection/share-usage/ShareUsageThreadDetail.tsx`
around lines 186 - 217, The current early-return loading gate uses
isLoadingMessages and ignores trace hydration (turnTraces and hydratedSpans),
causing TraceViewer to render with empty spans if a user switches to
Timeline/Raw before hydration finishes; update the loading logic in
ShareUsageThreadDetail to also consider turnTraces === undefined or
hydratedSpans === undefined (or add a separate trace-loading flag) so that
TraceViewer (or the Timeline/Raw tab render path) shows a spinner or defers
rendering until turnTraces and hydratedSpans are resolved; reference the
existing symbols isLoadingMessages, turnTraces, hydratedSpans, adaptedTrace, and
TraceViewer when making the change.
🧹 Nitpick comments (3)
mcpjam-inspector/client/src/components/connection/share-usage/ShareUsageThreadDetail.tsx (2)

164-173: Trace timing silently accepts zeroed/missing timestamps.

Math.min / Math.max over t.startedAt / t.endedAt will happily consume 0 or undefined values — the former anchors the timeline to the Unix epoch, the latter yields NaN. If the backend ever writes a partially populated turn row (e.g. a still-in-flight turn with endedAt: 0), the resulting traceEndedAtMs will either be misleading or non-finite. A quick filter((n) => Number.isFinite(n) && n > 0) before reducing would make this robust to such data without changing the happy path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@mcpjam-inspector/client/src/components/connection/share-usage/ShareUsageThreadDetail.tsx`
around lines 164 - 173, The trace timing computations for traceStartedAtMs and
traceEndedAtMs accept zero or non-finite timestamps which can produce misleading
or NaN results; update the useMemo callbacks that compute traceStartedAtMs and
traceEndedAtMs to first map turnTraces to the numeric timestamps and then filter
with Number.isFinite(n) && n > 0 (or equivalent) before calling
Math.min/Math.max, returning null when the filtered list is empty so the rest of
the component gets only valid positive timestamps.

37-54: Hydration fetches deserve the same cleanup courtesy as their messages-fetch neighbor.

The sibling effect at lines 75–110 aborts in-flight requests via AbortController and surfaces failures through console.error. hydrateSpans does neither: on rapid threadId/turnTraces churn, previous span downloads keep streaming bytes to a component that will never read them, and any parse/fetch failure vanishes silently — so a user staring at an empty timeline has no breadcrumb in DevTools explaining why. Threading an AbortSignal through and logging failures (permitted by the client guideline on console.*) would make debugging much kinder, and aligns both effects stylistically.

♻️ Sketch of a tidier hydration path
 async function hydrateSpans(
   traces: SharedChatTurnTrace[],
+  signal?: AbortSignal,
 ): Promise<EvalTraceSpan[]> {
   const results = await Promise.all(
     traces.map(async (trace) => {
       if (!trace.spansBlobUrl) return [];
       try {
-        const response = await fetch(trace.spansBlobUrl);
+        const response = await fetch(trace.spansBlobUrl, { signal });
         if (!response.ok) return [];
         const parsed = await response.json();
         return Array.isArray(parsed) ? (parsed as EvalTraceSpan[]) : [];
-      } catch {
+      } catch (err) {
+        if (err instanceof DOMException && err.name === "AbortError") return [];
+        console.error("Failed to hydrate trace spans:", err);
         return [];
       }
     }),
   );
   return results.flat();
 }
@@
   // Hydrate span blobs when turn traces arrive
   useEffect(() => {
     if (!turnTraces || turnTraces.length === 0) {
       setHydratedSpans([]);
       return;
     }

     let isActive = true;
-    void hydrateSpans(turnTraces).then((spans) => {
-      if (isActive) setHydratedSpans(spans);
-    });
+    const controller = new AbortController();
+    void hydrateSpans(turnTraces, controller.signal).then((spans) => {
+      if (isActive) setHydratedSpans(spans);
+    });
     return () => {
       isActive = false;
+      controller.abort();
     };
   }, [turnTraces]);

As per coding guidelines: "Browser console.* is acceptable for client-side debugging".

Also applies to: 113-127

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@mcpjam-inspector/client/src/components/connection/share-usage/ShareUsageThreadDetail.tsx`
around lines 37 - 54, The hydrateSpans function should accept an optional
AbortSignal and use it in fetch to allow aborting in-flight downloads and to
surface failures; update hydrateSpans(traces: SharedChatTurnTrace[], signal?:
AbortSignal): Promise<EvalTraceSpan[]> and pass the signal into
fetch(trace.spansBlobUrl, { signal }), then in the catch block console.error the
error along with identifying context (e.g., trace id or spansBlobUrl) and return
[]; also update the other similar helper (the one around lines 113–127) the same
way so both span-hydration paths support cancellation and log parse/fetch
failures.
mcpjam-inspector/server/utils/chat-ingestion.ts (1)

150-156: Keep Convex auth headers authoritative.

...options.forwardHeaders currently wins over authorization and content-type if a future caller passes conflicting keys. Put forwarded headers first so fixed ingestion headers cannot be shadowed.

Proposed defensive ordering
       headers: {
+        ...options.forwardHeaders,
         "content-type": "application/json",
         authorization: options.authHeader,
-        ...options.forwardHeaders,
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/server/utils/chat-ingestion.ts` around lines 150 - 156, The
fetch call building headers for the Convex ingest (the
fetch(`${convexUrl}/ingest-chat`...) invocation) currently spreads
options.forwardHeaders after the fixed "content-type" and authorization headers
so forwarded keys can shadow them; change the header merge order to spread
options.forwardHeaders first and then set "content-type" and authorization using
options.authHeader so the fixed ingestion headers (authorization and
content-type) are authoritative and cannot be overridden by forwardHeaders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcpjam-inspector/server/utils/chat-ingestion.ts`:
- Around line 12-27: The array ENRICHMENT_HEADERS_TO_FORWARD currently includes
raw IP headers ("x-forwarded-for", "x-real-ip", "cf-connecting-ip"); remove
these entries from ENRICHMENT_HEADERS_TO_FORWARD and stop forwarding them
verbatim, and instead compute a hashed visitor identifier before sending to
Convex (create or call a function like createVisitorHash / hashVisitorId on the
sender or in this module) and forward only a non-reversible value (e.g.,
"x-visitor-hash") plus the remaining non-IP enrichment headers; also update any
code paths that expect those raw IP headers (references to
ENRICHMENT_HEADERS_TO_FORWARD) to use the hashed field or document
retention/processing guarantees if you choose to keep them.

---

Outside diff comments:
In
`@mcpjam-inspector/client/src/components/connection/share-usage/ShareUsageThreadDetail.tsx`:
- Around line 186-217: The current early-return loading gate uses
isLoadingMessages and ignores trace hydration (turnTraces and hydratedSpans),
causing TraceViewer to render with empty spans if a user switches to
Timeline/Raw before hydration finishes; update the loading logic in
ShareUsageThreadDetail to also consider turnTraces === undefined or
hydratedSpans === undefined (or add a separate trace-loading flag) so that
TraceViewer (or the Timeline/Raw tab render path) shows a spinner or defers
rendering until turnTraces and hydratedSpans are resolved; reference the
existing symbols isLoadingMessages, turnTraces, hydratedSpans, adaptedTrace, and
TraceViewer when making the change.

---

Nitpick comments:
In
`@mcpjam-inspector/client/src/components/connection/share-usage/ShareUsageThreadDetail.tsx`:
- Around line 164-173: The trace timing computations for traceStartedAtMs and
traceEndedAtMs accept zero or non-finite timestamps which can produce misleading
or NaN results; update the useMemo callbacks that compute traceStartedAtMs and
traceEndedAtMs to first map turnTraces to the numeric timestamps and then filter
with Number.isFinite(n) && n > 0 (or equivalent) before calling
Math.min/Math.max, returning null when the filtered list is empty so the rest of
the component gets only valid positive timestamps.
- Around line 37-54: The hydrateSpans function should accept an optional
AbortSignal and use it in fetch to allow aborting in-flight downloads and to
surface failures; update hydrateSpans(traces: SharedChatTurnTrace[], signal?:
AbortSignal): Promise<EvalTraceSpan[]> and pass the signal into
fetch(trace.spansBlobUrl, { signal }), then in the catch block console.error the
error along with identifying context (e.g., trace id or spansBlobUrl) and return
[]; also update the other similar helper (the one around lines 113–127) the same
way so both span-hydration paths support cancellation and log parse/fetch
failures.

In `@mcpjam-inspector/server/utils/chat-ingestion.ts`:
- Around line 150-156: The fetch call building headers for the Convex ingest
(the fetch(`${convexUrl}/ingest-chat`...) invocation) currently spreads
options.forwardHeaders after the fixed "content-type" and authorization headers
so forwarded keys can shadow them; change the header merge order to spread
options.forwardHeaders first and then set "content-type" and authorization using
options.authHeader so the fixed ingestion headers (authorization and
content-type) are authoritative and cannot be overridden by forwardHeaders.
🪄 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: 27dc6388-3db3-4566-85a8-eff2d5762dab

📥 Commits

Reviewing files that changed from the base of the PR and between 16a1d50 and 1eda59d.

📒 Files selected for processing (5)
  • mcpjam-inspector/client/src/components/connection/share-usage/ShareUsageThreadDetail.tsx
  • mcpjam-inspector/client/src/hooks/useSharedChatThreads.ts
  • mcpjam-inspector/server/routes/mcp/chat-v2.ts
  • mcpjam-inspector/server/routes/web/chat-v2.ts
  • mcpjam-inspector/server/utils/chat-ingestion.ts

Comment on lines +12 to +27
const ENRICHMENT_HEADERS_TO_FORWARD = [
"user-agent",
"accept-language",
// Geo headers injected by CDN/edge providers
"cf-ipcountry",
"x-vercel-ip-country",
"x-vercel-ip-country-region",
"x-vercel-ip-city",
"x-geo-country",
"x-geo-region",
"x-geo-city",
// Client IP headers for visitor hashing
"x-forwarded-for",
"x-real-ip",
"cf-connecting-ip",
] as const;
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

🧩 Analysis chain

🏁 Script executed:

# First, let's read the full chat-ingestion.ts file to see the complete context
cat -n mcpjam-inspector/server/utils/chat-ingestion.ts

Repository: MCPJam/inspector

Length of output: 9443


🏁 Script executed:

# Search for usages of forwardHeaders to see how these headers are actually handled
rg "forwardHeaders" mcpjam-inspector/server/ -A 3 -B 1

Repository: MCPJam/inspector

Length of output: 2469


🏁 Script executed:

# Check if there's any hashing, filtering, or privacy handling of the IP headers
rg "x-forwarded-for|x-real-ip|cf-connecting-ip" mcpjam-inspector/server/ -A 2 -B 2

Repository: MCPJam/inspector

Length of output: 7029


🏁 Script executed:

# Look for any Convex calls or documentation about header handling
rg "Convex|header" mcpjam-inspector/server/utils/chat-ingestion.ts -A 2 -B 2

Repository: MCPJam/inspector

Length of output: 1506


🏁 Script executed:

# Look for any Convex-related comments or configuration about data handling
rg "Convex|retention|hash" mcpjam-inspector/server/ -i --type ts --type tsx

Repository: MCPJam/inspector

Length of output: 87


🏁 Script executed:

# Check if there's any filtering or transformation of forwardHeaders before they reach Convex
rg "ingest-chat|forwardHeaders" mcpjam-inspector/server/ -B 5 -A 5

Repository: MCPJam/inspector

Length of output: 12448


Avoid forwarding raw IP headers without Convex retention guarantees.

Headers like x-forwarded-for, x-real-ip, and cf-connecting-ip carry sensitive client IP data and can be spoofed. The code forwards these verbatim to Convex's /ingest-chat endpoint based only on an intent comment ("for visitor hashing") with no confirmation that Convex hashes them immediately or respects a short retention window. If visitor identification is the goal, hash on the sender side or document explicit retention/deletion guarantees from Convex.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/server/utils/chat-ingestion.ts` around lines 12 - 27, The
array ENRICHMENT_HEADERS_TO_FORWARD currently includes raw IP headers
("x-forwarded-for", "x-real-ip", "cf-connecting-ip"); remove these entries from
ENRICHMENT_HEADERS_TO_FORWARD and stop forwarding them verbatim, and instead
compute a hashed visitor identifier before sending to Convex (create or call a
function like createVisitorHash / hashVisitorId on the sender or in this module)
and forward only a non-reversible value (e.g., "x-visitor-hash") plus the
remaining non-IP enrichment headers; also update any code paths that expect
those raw IP headers (references to ENRICHMENT_HEADERS_TO_FORWARD) to use the
hashed field or document retention/processing guarantees if you choose to keep
them.

@chelojimenez chelojimenez merged commit 72c9638 into main Apr 19, 2026
9 of 11 checks passed
@chelojimenez chelojimenez deleted the chatbox-session-trace-views branch April 19, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant