fix(convex): remove "use node" directive from Convex examples#3321
Merged
richardsolomou merged 9 commits intomainfrom Apr 2, 2026
Merged
fix(convex): remove "use node" directive from Convex examples#3321richardsolomou merged 9 commits intomainfrom
richardsolomou merged 9 commits intomainfrom
Conversation
…ic sdk-trace-base Remove "use node" directive from all Convex example files by switching from @opentelemetry/sdk-node (Node.js-only) to @opentelemetry/sdk-trace-base with BasicTracerProvider + BatchSpanProcessor. Add a performance polyfill for Convex's V8 isolate which lacks the performance global that @opentelemetry/core expects.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📝 No Changeset FoundThis PR doesn't include a changeset. A changeset (and the release label) is required to release a new version. How to add a changesetRun this command and follow the prompts: pnpm changesetRemember: Never use |
Contributor
Prompt To Fix All With AIThis is a comment left during a code review.
Path: examples/example-convex/convex/polyfills.ts
Line: 6
Comment:
**`performance.now()` returns absolute timestamps, not relative time**
OpenTelemetry's `hrTime()` in `@opentelemetry/core` computes the absolute Unix timestamp as `performance.timeOrigin + performance.now()`. With the current polyfill, both return `Date.now()` (an absolute Unix ms value), so the sum is approximately `2 × Date.now()` — placing every span timestamp ~56 years in the future (around year 2096).
`performance.now()` is defined to return milliseconds *elapsed since the time origin*, not an absolute Unix timestamp. The fix is to capture `timeOrigin` once at creation time and have `now()` return the delta:
```suggestion
;(globalThis as any).performance = { timeOrigin: Date.now(), now: () => Date.now() - (globalThis as any).performance.timeOrigin }
```
Or equivalently:
```ts
const t0 = Date.now()
;(globalThis as any).performance = { timeOrigin: t0, now: () => Date.now() - t0 }
```
This ensures `timeOrigin + now()` correctly reconstructs the current Unix ms timestamp.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: examples/example-convex/convex/aiSdk/openTelemetry.ts
Line: 35
Comment:
**Global tracer provider set per-request will silently no-op on subsequent calls**
`trace.setGlobalTracerProvider()` in `@opentelemetry/api` v1.x only applies the first call — subsequent calls on the same global are silently ignored and return `false`. Since Convex V8 isolates are warm-reused across invocations, the provider registered by the *first* `generate` call (with its specific `distinctId` in the resource attributes) will be used for **all** subsequent calls. Every user's spans after the first will be attributed to that initial `distinctId`.
The same issue exists in `examples/example-convex/convex/convexAgent/openTelemetry.ts` at the equivalent `trace.setGlobalTracerProvider(provider)` call.
Consider initializing the provider at module scope (once per isolate lifetime) rather than per-invocation. Pass `distinctId` as a span attribute or via `metadata` in `experimental_telemetry` instead of resource attributes, so it is set per-span rather than per-provider registration.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "refactor(convex): replace @opentelemetry..." | Re-trigger Greptile |
Contributor
Contributor
|
Size Change: 0 B Total Size: 6.65 MB ℹ️ View Unchanged
|
trace.setGlobalTracerProvider() only applies on the first call — subsequent calls are silently ignored. Since Convex V8 isolates are warm-reused, the provider must be initialized at module scope. Per-request distinctId is already passed via experimental_telemetry metadata.
Move provider creation and global registration out of action files into a shared setup file. PostHogTraceExporter is now clearly framed as a standard OTEL span processor that sits alongside other exporters in the user's OTEL config, rather than owning the entire provider.
Keep the provider setup co-located with the action so each example is self-contained and easy to follow without jumping between files.
Convex actions terminate immediately after returning, so BatchSpanProcessor never gets a chance to flush. SimpleSpanProcessor sends spans synchronously, ensuring they're exported before the action completes.
andrewm4894
approved these changes
Apr 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Convex example files all required
"use node"because they depended on@opentelemetry/sdk-node, which is Node.js-specific. This forced Convex actions to run in a Node.js runtime instead of Convex's native V8 isolate, which is unnecessary and limits portability to other non-Node runtimes (Bun, Deno, edge).Changes
@opentelemetry/sdk-nodewith@opentelemetry/sdk-trace-base(runtime-agnostic) usingBasicTracerProvider+SimpleSpanProcessortrace.setGlobalTracerProvider()from@opentelemetry/apiinstead of the removedprovider.register()(v2 API change)setGlobalTracerProvidersilently no-ops on subsequent calls)convex/polyfills.tsto provide theperformanceglobal that@opentelemetry/coreexpects in Convex's V8 isolate, using a class-based polyfill with correct relative timing"use node"directive from all 5 Convex example files (aiSdk/openTelemetry,aiSdk/withTracing,aiSdk/manualCapture,convexAgent/openTelemetry,convexAgent/withTracing)user.idfrom OTEL resource attributes —distinctIdis already passed per-span viaexperimental_telemetry.metadataSimpleSpanProcessorinstead ofBatchSpanProcessor— Convex actions terminate immediately after returning, so batch processing never gets a chance to flushRelease info Sub-libraries affected
Libraries affected
Checklist
If releasing new changes
pnpm changesetto generate a changeset file