Skip to content

fix: external traces now respect parent sampling, and prevent broken traces when there is no external trace context #2395

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 15, 2025

Conversation

ericallam
Copy link
Member

No description provided.

Copy link

changeset-bot bot commented Aug 14, 2025

🦋 Changeset detected

Latest commit: e4d0edb

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

coderabbitai bot commented Aug 14, 2025

Walkthrough

Adds propagation of traceFlags throughout tracing code: parseTraceparent/serializeTraceparent now handle traceFlags; external trace context types and TraceContextManager include and propagate traceFlags; tracing SDK uses external traceFlags to determine sampling, gate span/log export, rebuild parent span contexts, and filter internal-only spans (using SEMATTRS_HTTP_URL). The webapp service now serializes traceFlags when propagating external trace context. A changeset entry records a patch release for trigger.dev about preserving parent sampling and preventing broken traces.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~35 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/otel-external-fixes

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ericallam ericallam force-pushed the fix/otel-external-fixes branch from 3e16462 to 747721c Compare August 14, 2025 16:18
Copy link
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: 6

🔭 Outside diff range comments (4)
packages/core/src/v3/isomorphic/traceContext.ts (1)

8-25: Harden traceparent parsing and update manager to reuse it

Short: I verified parseTraceparent only enforced 4 parts and found a direct split in the trace context manager that also needs the same validation. Apply the hardened parser and have the manager use it.

Files to change:

  • packages/core/src/v3/isomorphic/traceContext.ts — harden parser (accept 3/4 parts, validate hex lengths, non-zero IDs, normalize lowercase, drop invalid flags).
  • packages/core/src/v3/traceContext/manager.ts — stop manual split; call the hardened parseTraceparent so external tracecontext is validated consistently (split is at ~line 68).

Proposed diffs:

packages/core/src/v3/isomorphic/traceContext.ts

 export function parseTraceparent(
   traceparent?: string
 ): { traceId: string; spanId: string; traceFlags?: string } | undefined {
   if (!traceparent) {
     return undefined;
   }
-
-  const parts = traceparent.split("-");
-
-  if (parts.length !== 4) {
-    return undefined;
-  }
-
-  const [version, traceId, spanId, traceFlags] = parts;
-
-  if (version !== "00") {
-    return undefined;
-  }
-
-  if (!traceId || !spanId) {
-    return undefined;
-  }
-
-  return { traceId, spanId, traceFlags };
+  const parts = traceparent.split("-");
+
+  // Accept either 3- or 4-part headers (defensive), otherwise reject
+  if (parts.length !== 3 && parts.length !== 4) {
+    return undefined;
+  }
+
+  const [version, traceIdRaw, spanIdRaw, traceFlagsRaw] = parts;
+
+  if (version !== "00") {
+    return undefined;
+  }
+
+  if (!traceIdRaw || !spanIdRaw) {
+    return undefined;
+  }
+
+  const isHex = /^[0-9a-f]+$/i;
+  const traceId = traceIdRaw.toLowerCase();
+  const spanId = spanIdRaw.toLowerCase();
+  const traceFlagsCandidate =
+    typeof traceFlagsRaw === "string" ? traceFlagsRaw.toLowerCase() : undefined;
+
+  // Validate lengths, hex, and not-all-zero constraints
+  if (
+    traceId.length !== 32 ||
+    !isHex.test(traceId) ||
+    traceId === "00000000000000000000000000000000"
+  ) {
+    return undefined;
+  }
+  if (spanId.length !== 16 || !isHex.test(spanId) || spanId === "0000000000000000") {
+    return undefined;
+  }
+
+  // Validate flags if provided; drop invalid flags
+  const traceFlags =
+    traceFlagsCandidate && /^[0-9a-f]{2}$/.test(traceFlagsCandidate)
+      ? traceFlagsCandidate
+      : undefined;
+
+  return { traceId, spanId, traceFlags };
 }

packages/core/src/v3/traceContext/manager.ts

 import { Context, context, propagation, trace, TraceFlags } from "@opentelemetry/api";
 import { TraceContextManager } from "./types.js";
+import { parseTraceparent } from "../isomorphic/traceContext.js";
 
 export class StandardTraceContextManager implements TraceContextManager {
   public traceContext: Record<string, unknown> = {};
@@
   if ("traceparent" in traceContext && typeof traceContext.traceparent === "string") {
-    const [version, traceId, spanId, traceFlags] = traceContext.traceparent.split("-");
-
-    if (!traceId || !spanId) {
-      return undefined;
-    }
-
-    return {
-      traceId,
-      spanId,
-      traceFlags,
-      tracestate: tracestate,
-    };
+    const parsed = parseTraceparent(traceContext.traceparent);
+    if (!parsed) {
+      return undefined;
+    }
+
+    return {
+      traceId: parsed.traceId,
+      spanId: parsed.spanId,
+      traceFlags: parsed.traceFlags,
+      tracestate: tracestate,
+    };
   }

I ran a repo search — the only direct traceparent.split occurrences are:

  • packages/core/src/v3/isomorphic/traceContext.ts
  • packages/core/src/v3/traceContext/manager.ts (split at ~line 68)

Please apply both changes so all external traceparent parsing is consistent and validated. If you want strict W3C-only behavior instead, keep the 4-part check and reject 3-part headers.

packages/core/src/v3/traceContext/manager.ts (2)

1-3: Import and reuse shared parser to avoid duplication and inconsistencies

extractExternalTraceContext reimplements traceparent parsing. Prefer reusing the shared parseTraceparent to keep validation and normalization centralized.

Apply this diff to import the parser:

-import { Context, context, propagation, trace, TraceFlags } from "@opentelemetry/api";
+import { Context, context, propagation, trace, TraceFlags } from "@opentelemetry/api";
+import { parseTraceparent } from "../isomorphic/traceContext.js";

67-79: De-duplicate parsing and enforce validation via parseTraceparent()

Current code splits the string and doesn’t validate version/length/hex. Reuse parseTraceparent for correctness and consistency with isomorphic utilities.

Apply this diff:

-  if ("traceparent" in traceContext && typeof traceContext.traceparent === "string") {
-    const [version, traceId, spanId, traceFlags] = traceContext.traceparent.split("-");
-
-    if (!traceId || !spanId) {
-      return undefined;
-    }
-
-    return {
-      traceId,
-      spanId,
-      traceFlags,
-      tracestate: tracestate,
-    };
-  }
+  if ("traceparent" in traceContext && typeof traceContext.traceparent === "string") {
+    const parsed = parseTraceparent(traceContext.traceparent);
+    if (!parsed) {
+      return undefined;
+    }
+    return {
+      traceId: parsed.traceId,
+      spanId: parsed.spanId,
+      traceFlags: parsed.traceFlags,
+      tracestate,
+    };
+  }
packages/core/src/v3/otel/tracingSDK.ts (1)

413-415: Fix logs/spans correlation when there is no external trace context

Currently, logs skip traceId override if externalTraceContext is absent, but spans still use the generated fallback externalTraceId. This breaks correlation between spans and logs. Allow logs to use the fallback externalTraceId as well.

-    if (!logRecord.spanContext || !this.externalTraceId || !this.externalTraceContext) {
+    if (!logRecord.spanContext || !this.externalTraceId) {
       return logRecord;
     }
🧹 Nitpick comments (6)
packages/core/src/v3/traceContext/types.ts (1)

11-13: Document expected format of traceFlags (2-char hex, lowercase) and consider validation at boundaries

Adding traceFlags?: string is correct. To avoid propagation of malformed flags, please document the expected format and, where feasible, normalize/validate at parse/serialize boundaries (see comments in isomorphic/traceContext.ts).

Would you like me to add a lightweight validation note in the JSDoc here and ensure callers (parse/serialize) normalize to two lowercase hex chars?

apps/webapp/app/runEngine/services/triggerTask.server.ts (1)

380-385: Optional: tolerate older/lenient traceparent formats to avoid skipping propagation

parseTraceparent currently returns undefined for non-4-part headers. If you expect any upstream sources to send 3-part headers, consider making parseTraceparent tolerant (see suggested refactor in isomorphic/traceContext.ts). Otherwise, the code rightly falls back to the original traceContext unchanged.

Do we have any external integrations known to send non-compliant traceparent values? If yes, I can adjust parsing to keep propagation working while still validating IDs.

packages/core/src/v3/otel/tracingSDK.ts (4)

306-313: Dropping internal-only spans can create dangling parents; consider reparenting children

Filtering internal-only spans is correct, but children will still point to a parentSpanId that was removed, producing broken chains in external backends.

Optional refactor: in export(), precompute the set of spans you will drop (internal-only/unsampled), build a map spanId -> parentSpanContext, and reparent exported spans to the nearest exported ancestor (or undefined if none). This preserves a consistent tree after filtering.


356-356: Tighten types for exporter API

Match the SpanExporter signature to improve type safety.

-  export(spans: any[], resultCallback: (result: any) => void): void {
+  export(spans: ReadableSpan[], resultCallback: (result: any) => void): void {

364-364: Use OTEL diag logger for exporter errors

Prefer diag.error for consistency with OTEL diagnostics and central log control.

-      console.error(e);
+      diag.error("ExternalSpanExporterWrapper.export error", e as Error);

395-395: Tighten types for log exporter API

Align with LogRecordExporter for stronger type safety.

-  export(logs: any[], resultCallback: (result: any) => void): void {
+  export(logs: ReadableLogRecord[], resultCallback: (result: any) => void): void {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 944b187 and 747721c.

⛔ Files ignored due to path filters (4)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • references/d3-chat/package.json is excluded by !references/**
  • references/d3-chat/src/instrumentation.ts is excluded by !references/**
  • references/d3-chat/trigger.config.ts is excluded by !references/**
📒 Files selected for processing (6)
  • .changeset/violet-llamas-roll.md (1 hunks)
  • apps/webapp/app/runEngine/services/triggerTask.server.ts (1 hunks)
  • packages/core/src/v3/isomorphic/traceContext.ts (3 hunks)
  • packages/core/src/v3/otel/tracingSDK.ts (5 hunks)
  • packages/core/src/v3/traceContext/manager.ts (3 hunks)
  • packages/core/src/v3/traceContext/types.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/core/src/v3/traceContext/types.ts
  • packages/core/src/v3/traceContext/manager.ts
  • packages/core/src/v3/isomorphic/traceContext.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • packages/core/src/v3/otel/tracingSDK.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • packages/core/src/v3/traceContext/types.ts
  • packages/core/src/v3/traceContext/manager.ts
  • packages/core/src/v3/isomorphic/traceContext.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • packages/core/src/v3/otel/tracingSDK.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: In the webapp, all environment variables must be accessed through the env export of env.server.ts, instead of directly accessing process.env.
When importing from @trigger.dev/core in the webapp, never import from the root @trigger.dev/core path; always use one of the subpath exports as defined in the package's package.json.

Files:

  • apps/webapp/app/runEngine/services/triggerTask.server.ts
🧠 Learnings (1)
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : ALWAYS generate Trigger.dev tasks using the `task` function from `trigger.dev/sdk/v3` and export them as shown in the correct pattern.

Applied to files:

  • apps/webapp/app/runEngine/services/triggerTask.server.ts
🧬 Code Graph Analysis (2)
packages/core/src/v3/isomorphic/traceContext.ts (1)
apps/webapp/app/v3/eventRepository.server.ts (1)
  • traceId (1324-1336)
packages/core/src/v3/otel/tracingSDK.ts (2)
packages/core/src/v3/index.ts (1)
  • SemanticInternalAttributes (22-22)
packages/core/src/v3/semanticInternalAttributes.ts (1)
  • SemanticInternalAttributes (1-64)
🪛 GitHub Check: CodeQL
packages/core/src/v3/otel/tracingSDK.ts

[failure] 458-458: Incomplete URL substring sanitization
'https://api.trigger.dev' can be anywhere in the URL, and arbitrary hosts may come before or after it.


[failure] 462-462: Incomplete URL substring sanitization
'https://billing.trigger.dev' can be anywhere in the URL, and arbitrary hosts may come before or after it.


[failure] 466-466: Incomplete URL substring sanitization
'https://cloud.trigger.dev' can be anywhere in the URL, and arbitrary hosts may come before or after it.


[failure] 470-470: Incomplete URL substring sanitization
'https://engine.trigger.dev' can be anywhere in the URL, and arbitrary hosts may come before or after it.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: typecheck / typecheck
🔇 Additional comments (5)
.changeset/violet-llamas-roll.md (1)

5-5: Changelog entry is clear and scoped correctly

Patch-level note succinctly captures the behavioral fix for external traces and sampling propagation.

apps/webapp/app/runEngine/services/triggerTask.server.ts (1)

386-391: Good: propagate parent spanId and traceFlags to preserve sampling decision

Passing parsedTraceparent.traceFlags through to serializeTraceparent(...) ensures the sampling bit is preserved alongside the updated parent spanId. This aligns with the PR objective.

packages/core/src/v3/otel/tracingSDK.ts (3)

24-24: Good addition: use SEMATTRS_HTTP_URL for detection

Importing SEMATTRS_HTTP_URL aligns the check with OTEL semantic conventions and avoids magic strings.


346-347: Good guard to avoid broken traces when no external parent is available

Setting parentSpanContext to undefined for attempt spans without external context prevents constructing an invalid parent reference.


444-454: Internal-only span filter looks good

Skipping partial spans and the explicit url.path check for the ingest endpoint are appropriate safeguards.

Copy link
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: 0

♻️ Duplicate comments (2)
packages/core/src/v3/otel/tracingSDK.ts (2)

341-347: Bug: mapping external traceFlags using string equality drops sampling and ignores numeric flags

externalTraceContext.traceFlags is typed as a number, but the code checks typeof === "string" and compares to "01". This will always fall back to parentSpanContext?.traceFlags, losing the upstream sampling decision. Respect the numeric bitmask instead.

Apply this diff:

-        traceFlags:
-          typeof this.externalTraceContext.traceFlags === "string"
-            ? this.externalTraceContext.traceFlags === "01"
-              ? 1
-              : 0
-            : parentSpanContext?.traceFlags ?? 0,
+        traceFlags:
+          typeof this.externalTraceContext.traceFlags === "number"
+            ? (this.externalTraceContext.traceFlags & TraceFlags.SAMPLED)
+            : parentSpanContext?.traceFlags ?? TraceFlags.NONE,

444-476: Harden internal URL detection: avoid substring host checks; use strict hostname match

Using includes() on url.hostname can misclassify hosts (e.g., attacker.example.com/api.trigger.dev). Compare exact hostnames via a Set and check path equality for the ingest endpoint.

Apply this diff:

-  const internalHosts = [
-    "api.trigger.dev",
-    "billing.trigger.dev",
-    "cloud.trigger.dev",
-    "engine.trigger.dev",
-    "platform.trigger.dev",
-  ];
-
-  return (
-    internalHosts.some((host) => url.hostname.includes(host)) ||
-    url.pathname.includes("/api/v1/usage/ingest")
-  );
+  return INTERNAL_HOSTNAMES.has(url.hostname) || url.pathname === "/api/v1/usage/ingest";

Add this near the top of the file (outside the function) to replace the removed inline array:

const INTERNAL_HOSTNAMES = new Set([
  "api.trigger.dev",
  "billing.trigger.dev",
  "cloud.trigger.dev",
  "engine.trigger.dev",
  "platform.trigger.dev",
]);
🧹 Nitpick comments (2)
packages/core/src/v3/traceContext/manager.ts (2)

35-37: Nit: error message references the wrong method name

The message says “withExternalSpan” but the method is withExternalTrace.

Apply this diff:

-        "No active span found. withExternalSpan must be called within an active span context."
+        "No active span found. withExternalTrace must be called within an active span context."

63-75: Robust extraction with graceful failure on invalid traceparent

Parsing traceparent and returning undefined when invalid prevents broken traces from malformed inputs. The returned numeric traceFlags is ideal for downstream bitmask checks.

If you want to tighten types here, consider narrowing traceContext to Record<string, unknown> before property access to appease TS without any:

-function extractExternalTraceContext(traceContext: unknown) {
+function extractExternalTraceContext(traceContext: unknown) {
   if (typeof traceContext !== "object" || traceContext === null) {
     return undefined;
   }

-  const tracestate =
-    "tracestate" in traceContext && typeof traceContext.tracestate === "string"
+  const ctx = traceContext as Record<string, unknown>;
+  const tracestate =
+    typeof ctx.tracestate === "string"
       ? traceContext.tracestate
       : undefined;

-  if ("traceparent" in traceContext && typeof traceContext.traceparent === "string") {
-    const externalSpanContext = parseTraceParent(traceContext.traceparent);
+  if (typeof ctx.traceparent === "string") {
+    const externalSpanContext = parseTraceParent(ctx.traceparent);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 747721c and 2ded61d.

📒 Files selected for processing (3)
  • packages/core/src/v3/otel/tracingSDK.ts (6 hunks)
  • packages/core/src/v3/traceContext/manager.ts (3 hunks)
  • packages/core/src/v3/traceContext/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/v3/traceContext/types.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/core/src/v3/traceContext/manager.ts
  • packages/core/src/v3/otel/tracingSDK.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • packages/core/src/v3/traceContext/manager.ts
  • packages/core/src/v3/otel/tracingSDK.ts
🧬 Code Graph Analysis (2)
packages/core/src/v3/traceContext/manager.ts (3)
apps/webapp/app/runEngine/services/triggerTask.server.ts (1)
  • traceContext (355-399)
apps/webapp/app/presenters/v3/SpanPresenter.server.ts (1)
  • traceContext (600-620)
packages/core/src/v3/trace-context-api.ts (1)
  • traceContext (5-5)
packages/core/src/v3/otel/tracingSDK.ts (2)
packages/core/src/v3/index.ts (1)
  • SemanticInternalAttributes (22-22)
packages/core/src/v3/semanticInternalAttributes.ts (1)
  • SemanticInternalAttributes (1-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
packages/core/src/v3/traceContext/manager.ts (2)

3-3: Good addition: rely on OpenTelemetry's parser for correctness

Importing and using parseTraceParent centralizes W3C parsing and avoids homegrown parsing bugs.


40-45: Propagating external traceFlags into span context is correct

Using the external traceFlags (numeric) preserves upstream sampling decisions. This aligns with the PR goal of respecting parent sampling.

packages/core/src/v3/otel/tracingSDK.ts (4)

296-306: Correct: centralize sampling decision via bitmask helper

Initializing _isExternallySampled using isTraceFlagSampled(externalTraceContext?.traceFlags) keeps behavior consistent across spans and logs and respects the LSB sampling bit.


309-314: Early-return gating is sound

Short-circuiting span export when not externally sampled avoids emitting spans/logs for unsampled parent traces.


383-393: Consistent log gating with sampling bitmask

Mirrors span behavior; if parent isn’t sampled, emit nothing. This prevents “stray” logs for unsampled traces.


490-496: LGTM: sampling check uses numeric bitmask

(traceFlags & TraceFlags.SAMPLED) === TraceFlags.SAMPLED is the correct interpretation per W3C. Defaulting to sampled when flags are absent matches the current default behavior.

Copy link
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: 0

♻️ Duplicate comments (1)
packages/core/src/v3/otel/tracingSDK.ts (1)

439-471: Fix URL host/path checks: use exact hostname match and strict path comparison (CodeQL: incomplete URL substring sanitization)

Using includes() on url.hostname and url.pathname is vulnerable to false positives (e.g., evilapi.trigger.dev matches "api.trigger.dev"; "/api/v1/usage/ingestX" matches the ingest path). Parse and compare strictly.

Apply this diff within this function:

@@
-  const internalHosts = [
-    "api.trigger.dev",
-    "billing.trigger.dev",
-    "cloud.trigger.dev",
-    "engine.trigger.dev",
-    "platform.trigger.dev",
-  ];
-
-  return (
-    internalHosts.some((host) => url.hostname.includes(host)) ||
-    url.pathname.includes("/api/v1/usage/ingest")
-  );
+  return (
+    INTERNAL_HOSTNAMES.has(url.hostname) ||
+    url.pathname === "/api/v1/usage/ingest"
+  );

Add this module-level constant (outside the function) to avoid per-call allocations:

const INTERNAL_HOSTNAMES = new Set<string>([
  "api.trigger.dev",
  "billing.trigger.dev",
  "cloud.trigger.dev",
  "engine.trigger.dev",
  "platform.trigger.dev",
]);

This resolves the CodeQL “Incomplete URL substring sanitization” findings and prevents accidental matches.

🧹 Nitpick comments (2)
packages/core/src/v3/otel/tracingSDK.ts (2)

333-345: Propagate external traceFlags to parent and spanContext for consistency

Parent sampling is respected for gating, but exported spans will still carry their pre-existing traceFlags unless overridden. For consistency with the parent sampling decision and downstream processors, propagate the external traceFlags to both parentSpanContext (when rebuilding) and the span’s spanContext.

Apply this diff:

@@
     if (parentSpanContext) {
       parentSpanContext = {
         ...parentSpanContext,
-        traceId: externalTraceId,
+        traceId: externalTraceId,
+        traceFlags:
+          typeof this.externalTraceContext?.traceFlags === "number"
+            ? this.externalTraceContext.traceFlags
+            : parentSpanContext.traceFlags,
       };
     }
@@
     return {
       ...span,
-      spanContext: () => ({ ...spanContext, traceId: externalTraceId }),
+      spanContext: () => ({
+        ...spanContext,
+        traceId: externalTraceId,
+        traceFlags:
+          typeof this.externalTraceContext?.traceFlags === "number"
+            ? this.externalTraceContext.traceFlags
+            : spanContext.traceFlags,
+      }),
       parentSpanContext,
     };

Also applies to: 347-351


378-388: Type the exporter wrapper methods to OTEL types instead of any

The wrapper’s export signatures use any[], which loses type-safety and IDE help. Prefer ReadableLogRecord[]/ReadableSpan[] and the proper callback result type.

Apply these diffs:

@@
-class ExternalLogRecordExporterWrapper {
+class ExternalLogRecordExporterWrapper {
@@
-  export(logs: any[], resultCallback: (result: any) => void): void {
+  export(logs: ReadableLogRecord[], resultCallback: (result: unknown) => void): void {
@@
-    const modifiedLogs = logs.map(this.transformLogRecord.bind(this));
+    const modifiedLogs = logs.map(this.transformLogRecord.bind(this));
     this.underlyingExporter.export(modifiedLogs, resultCallback);
   }

And above, for the span wrapper:

@@
-  export(spans: any[], resultCallback: (result: any) => void): void {
+  export(spans: ReadableSpan[], resultCallback: (result: unknown) => void): void {

Note: If you want to be exact, you can import and use the concrete ExportResult type from OTEL instead of unknown, but the key improvement is avoiding any here.

Also applies to: 390-396

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2ded61d and e4d0edb.

📒 Files selected for processing (1)
  • packages/core/src/v3/otel/tracingSDK.ts (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/core/src/v3/otel/tracingSDK.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • packages/core/src/v3/otel/tracingSDK.ts
🧬 Code Graph Analysis (1)
packages/core/src/v3/otel/tracingSDK.ts (1)
packages/core/src/v3/semanticInternalAttributes.ts (1)
  • SemanticInternalAttributes (1-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
packages/core/src/v3/otel/tracingSDK.ts (6)

1-7: Importing TraceFlags to honor sampling bits — good change

Pulling in TraceFlags enables correct bitmask checks for sampling. This aligns with the PR objective to respect parent sampling.


30-30: Confirm SEMATTRS_HTTP_URL availability in the current semconv version

Some @opentelemetry/semantic-conventions versions expose different constant names. Ensure SEMATTRS_HTTP_URL resolves to "http.url" for the installed version to avoid undefined attribute lookups and false negatives in isSpanInternalOnly().

Would you like me to scan the repo for the installed semconv version and verify the constant’s presence via a quick script?


296-307: External sampling preserved via bitmask helper — LGTM

Using isTraceFlagSampled(externalTraceContext?.traceFlags) centralizes and correctly interprets the sampling bit. Defaulting to sampled when flags are absent addresses “prevent broken traces when there is no external trace context.”


309-316: Gate export on external sampling and filter internal-only spans — solid

Early-return to drop unsampled traces and internal-only spans reduces exporter load and matches the PR’s intent.


473-483: safeParseUrl is robust and side-effect free — good utility

Gracefully handles non-strings and parse errors; perfect for defensive URL parsing.


485-491: Sampling bitmask helper (isTraceFlagSampled) correctly uses TraceFlags.SAMPLED

This correctly interprets the sampling bit from numeric traceFlags and defaults to sampled when absent.

@ericallam ericallam merged commit 4d975b1 into main Aug 15, 2025
49 of 58 checks passed
@ericallam ericallam deleted the fix/otel-external-fixes branch August 15, 2025 09:12
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.

2 participants