Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,14 @@
"@hookform/resolvers": "^3.9.0",
"@iconify/react": "^5.1.0",
"@iizukak/codemirror-lang-wgsl": "^0.3.0",
"@langfuse/browser": "^5.9.1",
"@langfuse/otel": "^5.9.1",
"@modelcontextprotocol/sdk": "^1.29.0",
"@openrouter/ai-sdk-provider": "^2.2.3",
"@opentelemetry/api-logs": "^0.203.0",
"@opentelemetry/instrumentation": "^0.203.0",
"@opentelemetry/sdk-logs": "^0.203.0",
"@opentelemetry/sdk-node": "^0.55.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Incompatible sdk-node for Langfuse otel

High Severity

@langfuse/otel expects OpenTelemetry v2.x dependencies, but @opentelemetry/sdk-node@^0.55.0 pulls in v1.x components. This version mismatch can cause LangfuseSpanProcessor to fail or silently mis-export spans at runtime.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 68ea842. Configure here.

"@posthog/ai": "^7.18.7",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.5",
Expand Down Expand Up @@ -156,8 +159,6 @@
"graphql": "^16.9.0",
"http-status-codes": "^2.3.0",
"input-otp": "^1.4.2",
"langfuse": "^3.38.4",
"langfuse-vercel": "^3.38.4",
"linguist-languages": "^9.3.1",
"lucide-react": "^1.7.0",
"mermaid": "^11.16.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import { convertLLMOutputToPortableMarkdown } from "@/features/chat/utils";
import { submitFeedback } from "@/features/chat/actions";
import { isServiceError } from "@/lib/utils";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { LangfuseWeb } from "langfuse";
import { env } from "@sourcebot/shared/client";
import isEqual from "fast-deep-equal/react";
import { FileSource } from "@/features/chat/types";
import { LangfuseBrowserClient } from "@langfuse/browser";

interface AnswerCardProps {
answerText: string;
Expand All @@ -28,7 +28,7 @@ interface AnswerCardProps {
sources: FileSource[];
}

const langfuseWeb = env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY ? new LangfuseWeb({
const langfuseBrowser = env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY ? new LangfuseBrowserClient({
publicKey: env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
baseUrl: env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
}) : null;
Expand Down Expand Up @@ -90,7 +90,7 @@ const AnswerCardComponent = forwardRef<HTMLDivElement, AnswerCardProps>(({
messageId,
});

langfuseWeb?.score({
langfuseBrowser?.score({
traceId: traceId,
name: 'user_feedback',
value: feedbackType === 'like' ? 1 : 0,
Expand Down
19 changes: 12 additions & 7 deletions packages/web/src/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import * as Sentry from '@sentry/nextjs';
import { registerOTel } from '@vercel/otel';
import { LangfuseExporter } from 'langfuse-vercel';
import { LangfuseSpanProcessor } from '@langfuse/otel';
import { NodeSDK } from '@opentelemetry/sdk-node';

export async function register() {
if (
process.env.LANGFUSE_SECRET_KEY &&
process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY
) {
console.log("Registering Langfuse");
registerOTel({
const sdk = new NodeSDK({
serviceName: 'sourcebot',
traceExporter: new LangfuseExporter({
secretKey: process.env.LANGFUSE_SECRET_KEY,
publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
}),
spanProcessors: [
new LangfuseSpanProcessor({
secretKey: process.env.LANGFUSE_SECRET_KEY,
publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
}),
],
});
sdk.start();
registerOTel({ serviceName: 'sourcebot' });
Comment thread
cursor[bot] marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

NodeSDK import unsafe on edge

Medium Severity

@opentelemetry/sdk-node and LangfuseSpanProcessor are imported and started without a NEXT_RUNTIME === 'nodejs' guard, while Sentry in the same file is runtime-gated. instrumentation.ts loads for edge as well; a Node-only SDK at module scope can fail edge registration or pull Node APIs into the edge bundle.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 68ea842. Configure here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dual OTel SDK breaks Langfuse

High Severity

Langfuse is wired through a separate NodeSDK while registerOTel is started afterward with no LangfuseSpanProcessor. That is not equivalent to the old single registerOTel + exporter setup. OpenTelemetry allows only one global tracer provider, so one of the two bootstraps is ignored or left without Langfuse. Adding @opentelemetry/sdk-node@^0.55.0 also pulls OTel 1.28 APIs, which conflict with @langfuse/otel peers that expect OTel 2.x and a much newer OTLP HTTP exporter, so AI SDK spans can fail to reach Langfuse.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2fe231f. Configure here.

}
Comment on lines +3 to 24

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.

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Prevent Edge runtime crashes and OpenTelemetry provider conflicts.

There are two critical issues in this OpenTelemetry setup:

  1. Edge Runtime Crash: NodeSDK relies on Node.js native APIs. Because register() runs on both nodejs and edge runtimes, importing and instantiating NodeSDK at the top level will break the Edge build or crash at runtime.
  2. Duplicate OTel Providers: Calling sdk.start() and then immediately calling registerOTel() will instantiate two global tracer providers, leading to conflicts where Next.js route tracing is lost or OTel logs warnings.

If @vercel/otel's registerOTel is required for Next.js tracing, you should omit NodeSDK entirely and pass the LangfuseSpanProcessor directly to registerOTel. If you must use NodeSDK, you must dynamically import it and guard it behind a NEXT_RUNTIME === 'nodejs' check.

🛠️ Proposed fix (using `@vercel/otel` directly)
-import { LangfuseSpanProcessor } from '`@langfuse/otel`';
-import { NodeSDK } from '`@opentelemetry/sdk-node`';
 
 export async function register() {
     if (
         process.env.LANGFUSE_SECRET_KEY &&
-        process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY
+        process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY &&
+        process.env.NEXT_RUNTIME === 'nodejs'
     ) {
         console.log("Registering Langfuse");
-        const sdk = new NodeSDK({
-            serviceName: 'sourcebot',
-            spanProcessors: [
-                new LangfuseSpanProcessor({
-                    secretKey: process.env.LANGFUSE_SECRET_KEY,
-                    publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
-                    baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
-                }),
-            ],
-        });
-        sdk.start();
-        registerOTel({ serviceName: 'sourcebot' });
+        const { LangfuseSpanProcessor } = await import('`@langfuse/otel`');
+        
+        registerOTel({ 
+            serviceName: 'sourcebot',
+            spanProcessors: [
+                new LangfuseSpanProcessor({
+                    secretKey: process.env.LANGFUSE_SECRET_KEY,
+                    publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
+                    baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
+                }),
+            ],
+        });
     } else {
+        registerOTel({ serviceName: 'sourcebot' });
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { LangfuseSpanProcessor } from '@langfuse/otel';
import { NodeSDK } from '@opentelemetry/sdk-node';
export async function register() {
if (
process.env.LANGFUSE_SECRET_KEY &&
process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY
) {
console.log("Registering Langfuse");
registerOTel({
const sdk = new NodeSDK({
serviceName: 'sourcebot',
traceExporter: new LangfuseExporter({
secretKey: process.env.LANGFUSE_SECRET_KEY,
publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
}),
spanProcessors: [
new LangfuseSpanProcessor({
secretKey: process.env.LANGFUSE_SECRET_KEY,
publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
}),
],
});
sdk.start();
registerOTel({ serviceName: 'sourcebot' });
}
export async function register() {
if (
process.env.LANGFUSE_SECRET_KEY &&
process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY &&
process.env.NEXT_RUNTIME === 'nodejs'
) {
console.log("Registering Langfuse");
const { LangfuseSpanProcessor } = await import('`@langfuse/otel`');
registerOTel({
serviceName: 'sourcebot',
spanProcessors: [
new LangfuseSpanProcessor({
secretKey: process.env.LANGFUSE_SECRET_KEY,
publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
}),
],
});
} else {
registerOTel({ serviceName: 'sourcebot' });
}
}
🤖 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 `@packages/web/src/instrumentation.ts` around lines 3 - 24, Update register so
Edge execution never imports or instantiates the Node-only NodeSDK, and avoid
registering multiple global OpenTelemetry providers. Prefer removing NodeSDK and
passing the LangfuseSpanProcessor directly through registerOTel while preserving
the existing environment guard; if NodeSDK is retained, dynamically import it
only inside a NEXT_RUNTIME === 'nodejs' branch and do not call registerOTel
afterward.


if (process.env.NEXT_RUNTIME === 'nodejs') {
Expand Down
Loading