Skip to content

feat: OTEL metrics pipeline for task workers#3061

Draft
ericallam wants to merge 14 commits intomainfrom
feat/otel-metrics
Draft

feat: OTEL metrics pipeline for task workers#3061
ericallam wants to merge 14 commits intomainfrom
feat/otel-metrics

Conversation

@ericallam
Copy link
Member

@ericallam ericallam commented Feb 14, 2026

  • Adds an end-to-end OTEL metrics pipeline: task workers collect and export metrics via OpenTelemetry, the webapp ingests them into ClickHouse, and they're queryable through the existing dashboard query engine
  • Workers emit process CPU/memory metrics (via @opentelemetry/host-metrics) and Node.js runtime metrics (event loop utilization, event loop delay, heap usage)
  • Users can create custom metrics in their tasks via otel.metrics.getMeter() from @trigger.dev/sdk
  • Metrics are automatically tagged with run context (run ID, task slug, machine, worker version) so they can be sliced per-run, per-task, or per-machine
  • The TSQL query engine gains metrics table support with typed attribute columns, prettyFormat() for human-readable values, and per-schema time bucket thresholds
  • Includes reference tasks (references/hello-world/src/trigger/metrics.ts) demonstrating CPU-intensive, memory-ramp, bursty workload, and custom metrics patterns

What changed

Metrics collection (packages/core, packages/cli-v3)

  • Metrics export pipelineTracingSDK now sets up a MeterProvider with a PeriodicExportingMetricReader that chains through TaskContextMetricExporter (adds run context attributes) and BufferingMetricExporter (batches exports to reduce overhead)
  • Host metrics — Enabled @opentelemetry/host-metrics for process CPU, memory, and system-level metrics
  • Node.js runtime metrics — New nodejsRuntimeMetrics.ts module using performance.eventLoopUtilization(), monitorEventLoopDelay(), and process.memoryUsage() to emit 6 observable gauges
  • Custom metrics — Exposed otel.metrics from @trigger.dev/sdk so users can create counters, histograms, and gauges in their tasks
  • Machine ID — Stable per-worker machine identifier for grouping metrics
  • Dev worker — Drops system.* metrics to reduce noise, keeps sending metrics between runs in warm workers

Metrics ingestion (apps/webapp)

  • OTEL endpointotel.v1.metrics.ts accepts OTEL metric export requests (JSON and protobuf), converts to ClickHouse rows
  • ClickHouse schema016_create_metrics_v1.sql with 10-second aggregation buckets, JSON attributes column, 30-day TTL, and materialized views for 1m/5m rollups

Query engine (internal-packages/tsql, apps/webapp)

  • Metrics query schema — Typed columns for metric attributes (task_identifier, run_id, machine_name, worker_version, etc.) extracted from the JSON attributes column
  • prettyFormat() — TSQL function that annotates columns with format hints (bytes, percent, durationSeconds) for frontend rendering without changing the underlying data
  • Per-schema time buckets — Different tables can define their own time bucket thresholds (metrics uses tighter intervals than runs)
  • AI query integration — The AI query service knows about the metrics table and can generate metric queries
  • Chart improvements — Better formatting for byte values, percentages, and durations in charts and tables

Reference project

  • references/hello-world/src/trigger/metrics.ts — 6 example tasks: cpu-intensive, memory-ramp, bursty-workload, sustained-workload, concurrent-load, custom-metrics

Test plan

  • Build all packages and webapp
  • Start dev worker with hello-world reference project
  • Run cpu-intensive, memory-ramp, and custom-metrics tasks
  • Verify metrics in ClickHouse: SELECT DISTINCT metric_name FROM metrics_v1
  • Query via dashboard AI: "show me CPU utilization over time"
  • Verify prettyFormat renders correctly in chart tooltips and table cells
  • Confirm dev worker drops system.* metrics but keeps process.* and nodejs.*

@changeset-bot
Copy link

changeset-bot bot commented Feb 14, 2026

⚠️ No Changeset found

Latest commit: ed7e891

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 14, 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 per-column display format hints (ColumnFormatType) through tsql schema and printer and introduces format-aware formatters and utilities (bytes, decimalBytes, percent, duration, cost, quantity) used in charts, legends, tooltips, tables, and big-number cards. Adds per-table timeBucketThresholds and conditional FINAL handling for time-bucketing. Implements ClickHouse metrics support (table migration, insert helper, types), OTLP metrics ingest route, an OTLP->ClickHouse exporter, and extensive OTEL metrics plumbing across SDK, CLI, workers, and environment configuration (env vars, metric exporters/readers, metric buffering/processing, machine id propagation).

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main addition to the codebase: an OTEL metrics pipeline for task workers, which aligns with the primary objective of the PR.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all major changes across multiple subsystems. However, the description template requires a checklist and testing steps that are incomplete or only partially filled.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/otel-metrics

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
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: 16

Caution

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

⚠️ Outside diff range comments (1)
internal-packages/tsql/src/query/schema.ts (1)

306-330: ⚠️ Potential issue | 🟡 Minor

Prefer a type alias for OutputColumnMetadata.
Since this interface is being extended, converting it to a type keeps the file aligned with the TS guideline.

Suggested change
-export interface OutputColumnMetadata {
+export type OutputColumnMetadata = {
   /** Column name in the result set (after AS aliasing) */
   name: string;
   /** ClickHouse data type (from schema or inferred for computed expressions) */
   type: ClickHouseType;
   ...
-}
+};
As per coding guidelines, `**/*.{ts,tsx}`: Use types over interfaces for TypeScript.
🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/code/QueryResultsChart.tsx`:
- Around line 1111-1127: The bytes formatter block in QueryResultsChart.tsx can
mis-handle maxVal when it's -Infinity; update the formatter to guard against
non-finite maxVal by computing a safeMax (e.g., const safeMax =
Number.isFinite(maxVal) ? Math.abs(maxVal) : 1) and use safeMax in the
log/division math instead of Math.abs(maxVal || 1), keeping the existing value
=== 0 early return and the rest of the scaling logic (divisor, units, i
calculation, scaled, return string) unchanged.

In `@apps/webapp/app/components/code/TSQLResultsTable.tsx`:
- Around line 518-542: The current format-hint block is skipped whenever
column.customRenderType is set and also only handles
bytes/decimalBytes/percent/quantity, causing prettyFormat hints like "duration"
or "cost" (and any overrides on columns with customRenderType) to be ignored;
update the conditional to always check column.format regardless of
column.customRenderType and extend the switch in the format handling (the block
that checks column.format) to include "duration" and "cost" cases (using the
existing formatBytes/formatDecimalBytes/formatQuantity helpers as a model—call
the appropriate formatDuration/formatCost utilities or create them if missing)
so that prettyFormat hints are applied consistently.

In `@apps/webapp/app/routes/otel.v1.metrics.ts`:
- Around line 27-29: The response returned from
ExportMetricsServiceResponse.encode(exportResponse).finish() is missing the
Content-Type header; update the Response construction in the export handler (the
return new Response(...) line) to include headers: { "Content-Type":
"application/x-protobuf" } so clients can correctly interpret the protobuf
payload.
- Around line 13-17: The route currently casts the request body unsafely with
"body as ExportMetricsServiceRequest" before calling otlpExporter.exportMetrics,
which bypasses validation; replace this with a zod schema for the
ExportMetricsServiceRequest shape, parse/validate the raw JSON from
request.json() (throwing or returning a 4xx on parse failure), and pass the
validated/typed result to otlpExporter.exportMetrics so that
ExportMetricsServiceRequest, otlpExporter.exportMetrics, and request.json are
used with proper runtime validation rather than an unsafe cast.

In `@apps/webapp/app/services/queryService.server.ts`:
- Around line 155-160: The code currently picks a single time column using a
regex search over options.query which breaks for multi-table queries; update the
logic that computes timeColumn to (1) scan options.query for all referenced
table names from querySchemas (use word-boundary matches and avoid matching
inside string literals/comments by stripping strings/comments or, better, using
a lightweight SQL tokenizer/parser) and map each matched schema to its
timeConstraint, then (2) if the matched set of timeConstraint values contains
more than one distinct column, either reject the query with a clear error
(preferred) or implement per-table constraint enforcement in executeTSQL;
replace the single matchedSchema/timeColumn assignment with this multi-table
detection and add an explicit error path referencing querySchemas,
timeConstraint, options.query, timeColumn, and executeTSQL so callers get a
clear message when conflicting time columns are present.

In `@apps/webapp/app/utils/columnFormat.ts`:
- Around line 8-28: The bug is that for 0 < |bytes| < 1 the computed index i
becomes negative and selects units[-1]; in both formatBytes and
formatDecimalBytes clamp the computed index to be at least 0 and at most
units.length - 1 (e.g., compute rawIndex = Math.floor(Math.log(Math.abs(bytes))
/ Math.log(1024 or 1000)) then set i = Math.max(0, Math.min(rawIndex,
units.length - 1))). Update the calculations in the functions formatBytes and
formatDecimalBytes to use the clamped i before dividing and formatting.

In `@apps/webapp/app/v3/otlpExporter.server.ts`:
- Around line 490-512: The current value selection conflates a real 0 with
protobuf3 defaults by comparing dp.asDouble !== 0 and dp.asInt !== BigInt(0);
instead, detect which value field is actually set (use the protobuf oneof
discriminator such as dp.valueCase / dp.value?.case if available, or check
presence via hasOwnProperty/undefined checks for dp.asDouble vs dp.asInt) and
then choose that field (prefer dp.asDouble when present, even if 0, otherwise
use Number(dp.asInt)). Update the gauge handling where metric.gauge is iterated
(the dp value extraction before rows.push, referenced by metric.gauge,
dp.asDouble, dp.asInt, resolveDataPointContext, floorToTenSecondBucket) to use
the presence/oneof check and proper BigInt -> Number conversion.

In `@apps/webapp/app/v3/querySchemas.ts`:
- Around line 557-563: The attempt_number column is incorrectly defined as a
String which prevents numeric operations; update the attempt_number definition
to use a numeric ClickHouse type (e.g., change column("String", {...}) to
column("Int64", {...})), update the example to a numeric value (e.g., 1) and
keep the expression "attributes.trigger.attempt_number" and description
unchanged so the schema matches the metrics_v1 JSON typing and supports numeric
sorting/operations.

In `@apps/webapp/app/v3/services/aiQueryService.server.ts`:
- Line 58: Update the model string used to initialize the LanguageModelV1
instance to a currently supported Codex mini model: replace the literal
"codex-mini-latest" in the class field declaration (private readonly model:
LanguageModelV1 = openai("codex-mini-latest")) with a supported model name such
as "gpt-5-codex-mini" or "gpt-5.1-codex-mini" so the openai(...) call uses an
available model.

In `@internal-packages/tsql/src/query/time_buckets.ts`:
- Around line 20-27: Replace the exported interface BucketThreshold with a type
alias: change the declaration "export interface BucketThreshold {
maxRangeSeconds: number; interval: TimeBucketInterval; }" to "export type
BucketThreshold = { maxRangeSeconds: number; interval: TimeBucketInterval; }",
preserving the JSDoc comments and export so all usages of BucketThreshold
continue to work unchanged; no other logic changes are necessary.

In `@packages/core/package.json`:
- Around line 179-185: The package.json change updates public package
`@trigger.dev/core` but lacks a changeset; run pnpm run changeset:add to create a
changeset documenting the dependency updates to
`@opentelemetry/exporter-metrics-otlp-http`,
`@opentelemetry/exporter-trace-otlp-http`, `@opentelemetry/instrumentation`,
`@opentelemetry/sdk-logs`, `@opentelemetry/sdk-metrics` (and any related
`@opentelemetry` entries), fill in the change type/summary, save and commit the
generated changeset file alongside your package.json changes so the release
tooling will pick up the modification to `@trigger.dev/core`.
- Around line 179-185: Update the `@opentelemetry/host-metrics` dependency to a
0.200.x (unstable 2.0 line) release pinned to an exact version to match the
`@opentelemetry/sdk-metrics`@2.0.1 compatibility; specifically replace
"@opentelemetry/host-metrics": "^0.36.0" with an exact 0.200.x version (e.g.,
"0.200.0") in package.json so the host-metrics package line aligns with the SDK
2.0.x packages, or alternatively revert `@opentelemetry/sdk-metrics` back to the
1.10.x line if you prefer to keep host-metrics@0.36.0.

In `@packages/core/src/v3/index.ts`:
- Line 52: You added new public exports generateFriendlyId and fromFriendlyId
(export line in index.ts) which expands the package API; create and commit a
changeset so release tooling and changelogs capture this public change by
running the repository's changeset flow (e.g., run pnpm run changeset:add),
select the appropriate version bump and provide a brief summary mentioning
generateFriendlyId and fromFriendlyId, then commit the generated changeset file
alongside your export change.

In `@packages/core/src/v3/taskContext/index.ts`:
- Around line 105-112: The apiClient header injection currently checks only
taskContext.isInsideTask and therefore still injects worker headers when a run
is disabled; update the conditional in the api client (around the header
injection logic that references taskContext.isInsideTask) to also require
taskContext.isRunDisabled === false (e.g., if (taskContext.isInsideTask &&
!taskContext.isRunDisabled) { ... }), ensuring disable() (which sets
_runDisabled via disable()) prevents injection; verify related symbols:
disable(), setGlobalTaskContext(), isInsideTask, isRunDisabled, and the header
keys "x-trigger-worker"/"x-trigger-engine-version" are guarded by the new
condition.

In `@packages/core/src/v3/taskContext/otelProcessors.ts`:
- Around line 143-173: The code is injecting high-cardinality attributes into
metric data via contextAttrs (in the block using taskContext.isRunDisabled and
the else branch) which adds IDs and free-form values; remove ENVIRONMENT_ID,
ORGANIZATION_ID, PROJECT_ID, RUN_ID, WORKER_ID, TASK_SLUG, ATTEMPT_NUMBER, and
RUN_TAGS from the per-data-point metric attributes set on contextAttrs and
instead either: 1) move any needed identifiers to the Resource once (use the
Resource API when initializing telemetry) for correlation, or 2) drop them
entirely and only keep low-cardinality attributes (ENVIRONMENT_TYPE and
MACHINE_PRESET_NAME) in contextAttrs; update the logic that conditionally adds
RUN_TAGS and worker fields (the if (!taskContext.isRunDisabled &&
ctx.run.tags?.length) and the if (taskContext.worker) blocks) to avoid attaching
high-cardinality values to metrics.
🧹 Nitpick comments (14)
packages/core/src/v3/taskContext/otelProcessors.ts (3)

224-235: Buffer grows without an upper bound between flush intervals.

If the flush interval is long or export calls are frequent, _buffer accumulates indefinitely until the next time-based flush. Consider adding a max buffer size cap that triggers an early flush to prevent unbounded memory growth.

💡 Proposed safeguard
+  private static readonly MAX_BUFFER_SIZE = 100;
+
   export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void {
     this._buffer.push(metrics);
 
     const now = Date.now();
-    if (now - this._lastFlushTime >= this._flushIntervalMs) {
+    if (now - this._lastFlushTime >= this._flushIntervalMs || this._buffer.length >= BufferingMetricExporter.MAX_BUFFER_SIZE) {
       this._lastFlushTime = now;
       const merged = this._mergeBuffer();
       this._innerExporter.export(merged, resultCallback);

258-306: Merge uses only the first batch's resource and keys scopes by name alone.

  1. If different ResourceMetrics entries carry different resource objects (e.g., after a resource attribute update), all but the first are silently dropped (Line 300).
  2. Scopes are keyed solely by scope.name (Line 273), so two scopes with the same name but different version or schemaUrl would be merged incorrectly.

These are edge cases but worth a defensive check or at least a comment documenting the assumption.

💡 Use composite scope key
-        const scopeKey = sm.scope.name;
+        const scopeKey = `${sm.scope.name}@${sm.scope.version ?? ""}`;

118-130: Duplicated delegation boilerplate across both exporters.

Both TaskContextMetricExporter and BufferingMetricExporter have identical constructor logic for forwarding selectAggregationTemporality and selectAggregation. Consider extracting a small helper or base class to DRY this up if more decorating exporters are planned.

Also applies to: 204-222

apps/webapp/app/v3/services/aiQueryService.server.ts (2)

377-382: Consider extracting the shared prompt sections into helper methods to reduce duplication.

The "Choosing the Right Table" block, syntax guide, and common patterns are nearly identical between buildSystemPrompt and buildEditSystemPrompt. Extracting shared sections (e.g., buildTableSelectionGuide(), buildSyntaxGuide()) would make future prompt updates less error-prone.

Also applies to: 525-529


589-594: Edit-mode prompt has fewer metrics examples than new-mode.

The new-mode prompt includes detailed example queries for CPU and memory metrics (lines 465–483) and the full list of prettyFormat types (line 461), while the edit-mode prompt omits these. If the user asks to edit a query to add metrics columns or switch tables, the AI will have less guidance. Consider whether this asymmetry is intentional.

packages/cli-v3/src/dev/taskRunProcessPool.ts (2)

44-44: Silence the Biome warning by using a block body in the forEach callback.

The arrow shorthand implicitly returns the result of clearIdleTimer, which Biome flags as useIterableCallbackReturn. Use a block body to make the intent explicit.

♻️ Proposed fix
-    processesToKill.forEach((process) => this.clearIdleTimer(process));
+    processesToKill.forEach((process) => {
+      this.clearIdleTimer(process);
+    });

226-243: Unhandled promise from killProcess in the idle timeout callback.

this.killProcess(process) returns a Promise that is neither awaited nor .catch()-ed. While killProcess has internal error handling, any unexpected rejection would become an unhandled promise rejection. Consider adding a .catch():

🛡️ Proposed fix
-      this.killProcess(process);
+      this.killProcess(process).catch((err) => {
+        logger.debug("[TaskRunProcessPool] Error killing idle process", { err });
+      });
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (1)

966-976: TRIGGER_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS reuses the export interval value.

Both in dev (here) and prod (line 1124), the export timeout is set to the same value as the export interval. This means a slow export could consume the entire interval window before the next export fires. If this is intentional as a "timeout should never exceed interval" heuristic, a brief comment would help future readers. Otherwise, consider exposing a separate DEV_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS env var.

internal-packages/clickhouse/schema/016_create_metrics_v1.sql (1)

1-37: Confirm whether this should follow the raw_ input-table naming convention.
If trigger_dev.metrics_v1 is the ingest/raw metrics table, consider renaming to trigger_dev.raw_metrics_v1 (or adding a raw table + MV) to match the repo’s naming rule.
As per coding guidelines, internal-packages/clickhouse/schema/**/*.sql: Follow ClickHouse naming conventions: raw_ prefix for input tables, _v1, _v2 suffixes for versioning, _mv_v1 suffix for materialized views.

internal-packages/tsql/src/query/printer.ts (1)

944-988: Consider centralizing the allowed prettyFormat types.
The hard‑coded validFormats list can drift from ColumnFormatType/formatters; a shared exported const would keep things in sync.

apps/webapp/app/components/primitives/charts/ChartBar.tsx (1)

170-188: tooltipValueFormatter is silently ignored when no tooltipLabelFormatter is provided.

In the bar chart, value formatting only takes effect when tooltipLabelFormatter is supplied (line 175), because otherwise the ZoomTooltip component is rendered. This differs from ChartLine.tsx where the formatter is always passed to ChartTooltipContent. If this is intentional (zoom tooltip has its own format), no action needed — but worth confirming this asymmetry is desired.

apps/webapp/app/routes/otel.v1.metrics.ts (1)

36-40: Consider using a structured logger instead of console.error.

Other parts of the webapp use a structured logger for better observability. Also, logging the raw error object might inadvertently leak sensitive data in production logs.

apps/webapp/app/v3/otlpExporter.server.ts (1)

575-582: Use type instead of interface per coding guidelines.

As per coding guidelines: "Use types over interfaces for TypeScript."

Suggested fix
-interface ResourceContext {
-  taskSlug: string | undefined;
-  runId: string | undefined;
-  attemptNumber: number | undefined;
-  machineId: string | undefined;
-  workerId: string | undefined;
-  workerVersion: string | undefined;
-}
+type ResourceContext = {
+  taskSlug: string | undefined;
+  runId: string | undefined;
+  attemptNumber: number | undefined;
+  machineId: string | undefined;
+  workerId: string | undefined;
+  workerVersion: string | undefined;
+};
packages/core/src/v3/otel/tracingSDK.ts (1)

340-354: flush() and shutdown() correctly include the meter provider.

Using Promise.all for parallel shutdown/flush of all three providers is appropriate. One consideration: if one provider's shutdown rejects, Promise.all will reject immediately, potentially leaving other providers in an inconsistent state. Consider Promise.allSettled for shutdown() to ensure all providers get a chance to clean up.

Optional: use allSettled for more resilient shutdown
  public async shutdown() {
-    await Promise.all([
+    await Promise.allSettled([
       this._traceProvider.shutdown(),
       this._logProvider.shutdown(),
       this._meterProvider.shutdown(),
     ]);
   }

Comment on lines +1111 to +1127
if (format === "bytes" || format === "decimalBytes") {
const divisor = format === "bytes" ? 1024 : 1000;
const units =
format === "bytes"
? ["B", "KiB", "MiB", "GiB", "TiB"]
: ["B", "KB", "MB", "GB", "TB"];
return (value: number): string => {
if (value === 0) return "0 B";
// Use consistent unit for all ticks based on max value
const i = Math.min(
Math.floor(Math.log(Math.abs(maxVal || 1)) / Math.log(divisor)),
units.length - 1
);
const scaled = value / Math.pow(divisor, i);
return `${scaled.toFixed(scaled < 10 ? 1 : 0)} ${units[i]}`;
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Edge case: maxVal could be -Infinity when the formatter is invoked with bytes format.

If maxVal remains -Infinity (e.g., all values are null/non-numeric), then Math.abs(maxVal || 1) evaluates to Math.abs(-Infinity) = Infinity because -Infinity is truthy. This makes i clamp to the last unit index, so a non-zero value would be formatted as e.g. "0.0 TiB" instead of "0 B". In practice, the early returns at lines 977–983 prevent rendering an empty chart, so this is unlikely to trigger. But a small guard would make the formatter more robust.

Proposed defensive fix
     return (value: number): string => {
       if (value === 0) return "0 B";
       // Use consistent unit for all ticks based on max value
+      const refValue = isFinite(maxVal) ? Math.abs(maxVal) : Math.abs(value);
       const i = Math.min(
-        Math.floor(Math.log(Math.abs(maxVal || 1)) / Math.log(divisor)),
+        Math.floor(Math.log(refValue || 1) / Math.log(divisor)),
         units.length - 1
       );
🤖 Prompt for AI Agents
In `@apps/webapp/app/components/code/QueryResultsChart.tsx` around lines 1111 -
1127, The bytes formatter block in QueryResultsChart.tsx can mis-handle maxVal
when it's -Infinity; update the formatter to guard against non-finite maxVal by
computing a safeMax (e.g., const safeMax = Number.isFinite(maxVal) ?
Math.abs(maxVal) : 1) and use safeMax in the log/division math instead of
Math.abs(maxVal || 1), keeping the existing value === 0 early return and the
rest of the scaling logic (divisor, units, i calculation, scaled, return string)
unchanged.

Comment on lines +13 to +17
const body = await request.json();

const exportResponse = await otlpExporter.exportMetrics(
body as ExportMetricsServiceRequest
);
Copy link
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 look at the actual file
head -50 apps/webapp/app/routes/otel.v1.metrics.ts

Repository: triggerdotdev/trigger.dev

Length of output: 1426


🏁 Script executed:

# Search for ExportMetricsServiceRequest to understand where it comes from
rg 'ExportMetricsServiceRequest' -n --type ts apps/webapp/app/routes/otel.v1.metrics.ts

Repository: triggerdotdev/trigger.dev

Length of output: 239


🏁 Script executed:

# Check what ExportMetricsServiceRequest is imported from
rg 'import.*ExportMetricsServiceRequest' -n --type ts

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

# Check if ExportMetricsServiceRequest has a fromJSON method in the codebase
rg -B5 -A10 'fromJSON' --type ts | head -100

Repository: triggerdotdev/trigger.dev

Length of output: 11593


🏁 Script executed:

# Search for ExportMetricsServiceRequest definition and its fromJSON method
fd 'metrics' --type f -e ts internal-packages/otlp-importer/src/generated/ | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 262


🏁 Script executed:

# Look specifically for ExportMetricsServiceRequest
rg 'ExportMetricsServiceRequest' internal-packages/otlp-importer/src/generated -A 30 | grep -E '(fromJSON|class|interface|export)'

Repository: triggerdotdev/trigger.dev

Length of output: 2544


🏁 Script executed:

# Check how zod is used in the webapp for validation of API inputs
rg 'zod|z\.parse|z\.validate' apps/webapp/app/routes -l --type ts | head -10

Repository: triggerdotdev/trigger.dev

Length of output: 758


🏁 Script executed:

# Look at a specific example of zod validation in webapp routes
head -80 apps/webapp/app/routes/admin.api.v1.environments.\$environmentId.ts

Repository: triggerdotdev/trigger.dev

Length of output: 2326


🏁 Script executed:

# Check if there's a pattern for API body validation using zod in the file or nearby
rg -A5 'z\.parse|request\.json()' apps/webapp/app/routes/admin.api.v1.environments.\$environmentId.ts --type ts

Repository: triggerdotdev/trigger.dev

Length of output: 272


🏁 Script executed:

# Let's examine the full ExportMetricsServiceRequest.fromJSON implementation
rg -A 20 'fromJSON\(object: any\): ExportMetricsServiceRequest' internal-packages/otlp-importer/src/generated/opentelemetry/proto/collector/metrics/v1/metrics_service.ts

Repository: triggerdotdev/trigger.dev

Length of output: 928


Use zod validation for the JSON body instead of an unsafe cast.

The unvalidated cast body as ExportMetricsServiceRequest bypasses type safety. Per the coding guidelines, zod should be used for validation in apps/webapp. Define a validation schema for the request and parse the body against it:

      const body = await request.json();
+     const exportRequest = ExportMetricsServiceRequest.fromJSON(body);
+
      const exportResponse = await otlpExporter.exportMetrics(
-       body as ExportMetricsServiceRequest
+       exportRequest
      );

Note: While ExportMetricsServiceRequest.fromJSON() is available in the generated protobuf code, consider using a zod schema instead to align with project validation standards in the webapp.

🤖 Prompt for AI Agents
In `@apps/webapp/app/routes/otel.v1.metrics.ts` around lines 13 - 17, The route
currently casts the request body unsafely with "body as
ExportMetricsServiceRequest" before calling otlpExporter.exportMetrics, which
bypasses validation; replace this with a zod schema for the
ExportMetricsServiceRequest shape, parse/validate the raw JSON from
request.json() (throwing or returning a 4xx on parse failure), and pass the
validated/typed result to otlpExporter.exportMetrics so that
ExportMetricsServiceRequest, otlpExporter.exportMetrics, and request.json are
used with proper runtime validation rather than an unsafe cast.

Comment on lines +27 to +29
return new Response(ExportMetricsServiceResponse.encode(exportResponse).finish(), {
status: 200,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing Content-Type header on protobuf response.

The protobuf response should include Content-Type: application/x-protobuf so clients can correctly interpret the response format.

Suggested fix
       return new Response(ExportMetricsServiceResponse.encode(exportResponse).finish(), {
         status: 200,
+        headers: { "Content-Type": "application/x-protobuf" },
       });
🤖 Prompt for AI Agents
In `@apps/webapp/app/routes/otel.v1.metrics.ts` around lines 27 - 29, The response
returned from ExportMetricsServiceResponse.encode(exportResponse).finish() is
missing the Content-Type header; update the Response construction in the export
handler (the return new Response(...) line) to include headers: {
"Content-Type": "application/x-protobuf" } so clients can correctly interpret
the protobuf payload.

Comment on lines +155 to +160
// Detect which table the query targets to determine the time column
// Each table schema declares its primary time column via timeConstraint
const matchedSchema = querySchemas.find((s) =>
new RegExp(`\\bFROM\\s+${s.name}\\b`, "i").test(options.query)
);
const timeColumn = matchedSchema?.timeConstraint ?? "triggered_at";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find the querySchemas definition and schema declarations
rg -n "querySchemas" apps/webapp/app/services/queryService.server.ts -A 5 -B 5

Repository: triggerdotdev/trigger.dev

Length of output: 1435


🏁 Script executed:

#!/bin/bash
# Find all timeConstraint declarations across the codebase
rg -n "timeConstraint" apps/webapp/ --type=ts -C 2

Repository: triggerdotdev/trigger.dev

Length of output: 1739


🏁 Script executed:

#!/bin/bash
# Check the full context around lines 155-160 and surrounding code
sed -n '140,260p' apps/webapp/app/services/queryService.server.ts | cat -n

Repository: triggerdotdev/trigger.dev

Length of output: 5691


🏁 Script executed:

#!/bin/bash
# Search for JOIN queries in tests or examples
rg -n "JOIN" apps/webapp/ --type=ts -C 2 | head -100

Repository: triggerdotdev/trigger.dev

Length of output: 8142


🏁 Script executed:

#!/bin/bash
# Find the executeTSQL function to understand how it handles enforcedWhereClause
rg -n "executeTSQL" apps/webapp/ --type=ts -l

Repository: triggerdotdev/trigger.dev

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Check if there are any documented multi-table queries or comments about JOIN support
rg -n "JOIN\|multi.table\|multiple.table" apps/webapp/app/services/queryService.server.ts -i -C 3

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

#!/bin/bash
# Look at the full querySchemas to see all available tables
head -450 apps/webapp/app/v3/querySchemas.ts | tail -50

Repository: triggerdotdev/trigger.dev

Length of output: 1780


🏁 Script executed:

#!/bin/bash
# Find the executeTSQL implementation
find apps/webapp -name "*.ts" -type f | xargs grep -l "executeTSQL" | head -10

Repository: triggerdotdev/trigger.dev

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Look for the actual executeTSQL function definition
rg -n "^(export\s+)?(async\s+)?function executeTSQL|const executeTSQL" apps/webapp --type=ts -A 20

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

#!/bin/bash
# Check if executeTSQL is imported from an external package
rg -n "executeTSQL" apps/webapp/app/services/queryService.server.ts -B 30 | grep -E "import|from"

Repository: triggerdotdev/trigger.dev

Length of output: 279


🏁 Script executed:

#!/bin/bash
# Search more carefully for where executeTSQL is defined or imported
rg "executeTSQL" apps/webapp/app/services/queryService.server.ts -B 50 | head -100

Repository: triggerdotdev/trigger.dev

Length of output: 2161


🏁 Script executed:

#!/bin/bash
# Search for executeTSQL in the entire webapp to find where it's defined
fd -e ts -e tsx apps/webapp | xargs grep -l "executeTSQL"

Repository: triggerdotdev/trigger.dev

Length of output: 420


🏁 Script executed:

#!/bin/bash
# Look at imports at the top of queryService.server.ts
head -50 apps/webapp/app/services/queryService.server.ts

Repository: triggerdotdev/trigger.dev

Length of output: 1866


🏁 Script executed:

#!/bin/bash
# Check if there are any query examples or integration tests that show multi-table queries being used
rg -n "FROM.*JOIN|JOIN.*metrics|JOIN.*runs" apps/webapp/app --type=ts -C 2

Repository: triggerdotdev/trigger.dev

Length of output: 4566


🏁 Script executed:

#!/bin/bash
# Verify the test example to see how queryService is tested with queries
rg -n "executeQuery\|queryService" apps/webapp/test --type=ts -l 2>/dev/null | head -5

Repository: triggerdotdev/trigger.dev

Length of output: 51


Multi-table queries with different time columns will fail or apply incorrect constraints.

When a query JOINs tables with different timeConstraint values (e.g., FROM runs JOIN metrics_v1), the regex-based detection at line 157–158 matches only the first table name, causing timeColumn to be set incorrectly for the other table. For instance, runs uses "triggered_at" while metrics_v1 uses "bucket_start". The enforced WHERE clause at line 206 and fallback at line 248 will then reference the wrong column name for the non-primary table, leading to query failures or silently incorrect filtering.

The regex can also match table names inside string literals or comments, though this is a lower-risk edge case.

Verify whether the downstream executeTSQL layer handles per-table constraint enforcement, or whether multi-table queries should be explicitly rejected if they involve tables with conflicting time columns.

🧰 Tools
🪛 ast-grep (0.40.5)

[warning] 157-157: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\bFROM\\s+${s.name}\\b, "i")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
In `@apps/webapp/app/services/queryService.server.ts` around lines 155 - 160, The
code currently picks a single time column using a regex search over
options.query which breaks for multi-table queries; update the logic that
computes timeColumn to (1) scan options.query for all referenced table names
from querySchemas (use word-boundary matches and avoid matching inside string
literals/comments by stripping strings/comments or, better, using a lightweight
SQL tokenizer/parser) and map each matched schema to its timeConstraint, then
(2) if the matched set of timeConstraint values contains more than one distinct
column, either reject the query with a clear error (preferred) or implement
per-table constraint enforcement in executeTSQL; replace the single
matchedSchema/timeColumn assignment with this multi-table detection and add an
explicit error path referencing querySchemas, timeConstraint, options.query,
timeColumn, and executeTSQL so callers get a clear message when conflicting time
columns are present.

Comment on lines +20 to +27
* A threshold mapping a maximum time range duration to a bucket interval.
*/
export interface BucketThreshold {
/** Maximum range duration in seconds for this threshold to apply */
maxRangeSeconds: number;
/** The bucket interval to use when the range is under maxRangeSeconds */
interval: TimeBucketInterval;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a type alias for BucketThreshold.
The TS guideline prefers type aliases over interface for new definitions.

Suggested change
-export interface BucketThreshold {
+export type BucketThreshold = {
   /** Maximum range duration in seconds for this threshold to apply */
   maxRangeSeconds: number;
   /** The bucket interval to use when the range is under maxRangeSeconds */
   interval: TimeBucketInterval;
-}
+};
As per coding guidelines, `**/*.{ts,tsx}`: Use types over interfaces for TypeScript.
📝 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
* A threshold mapping a maximum time range duration to a bucket interval.
*/
export interface BucketThreshold {
/** Maximum range duration in seconds for this threshold to apply */
maxRangeSeconds: number;
/** The bucket interval to use when the range is under maxRangeSeconds */
interval: TimeBucketInterval;
}
* A threshold mapping a maximum time range duration to a bucket interval.
*/
export type BucketThreshold = {
/** Maximum range duration in seconds for this threshold to apply */
maxRangeSeconds: number;
/** The bucket interval to use when the range is under maxRangeSeconds */
interval: TimeBucketInterval;
};
🤖 Prompt for AI Agents
In `@internal-packages/tsql/src/query/time_buckets.ts` around lines 20 - 27,
Replace the exported interface BucketThreshold with a type alias: change the
declaration "export interface BucketThreshold { maxRangeSeconds: number;
interval: TimeBucketInterval; }" to "export type BucketThreshold = {
maxRangeSeconds: number; interval: TimeBucketInterval; }", preserving the JSDoc
comments and export so all usages of BucketThreshold continue to work unchanged;
no other logic changes are necessary.

Comment on lines +179 to +185
"@opentelemetry/exporter-metrics-otlp-http": "0.203.0",
"@opentelemetry/host-metrics": "^0.36.0",
"@opentelemetry/exporter-trace-otlp-http": "0.203.0",
"@opentelemetry/instrumentation": "0.203.0",
"@opentelemetry/resources": "2.0.1",
"@opentelemetry/sdk-logs": "0.203.0",
"@opentelemetry/sdk-metrics": "2.0.1",
Copy link
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:

# Check if a changeset was added in this PR for `@trigger.dev/core`
fd --type f '.' '.changeset' --exec grep -l '@trigger.dev/core' {} 2>/dev/null || echo "No changeset found referencing `@trigger.dev/core`"

Repository: triggerdotdev/trigger.dev

Length of output: 225


🏁 Script executed:

# Also check the actual package.json to verify it's a public package
cat packages/core/package.json | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 503


🏁 Script executed:

# List changesets to see what exists
ls -la .changeset/ 2>/dev/null | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 914


🏁 Script executed:

# Check if these are new dependencies or version updates by examining git diff
git diff HEAD -- packages/core/package.json | head -50

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

# Also check the specific lines mentioned in the review
sed -n '179,185p' packages/core/package.json

Repository: triggerdotdev/trigger.dev

Length of output: 407


Add a changeset for modifications to this public package.

@trigger.dev/core is a public package, and modifications require a changeset. Run pnpm run changeset:add to document these dependency changes.

🤖 Prompt for AI Agents
In `@packages/core/package.json` around lines 179 - 185, The package.json change
updates public package `@trigger.dev/core` but lacks a changeset; run pnpm run
changeset:add to create a changeset documenting the dependency updates to
`@opentelemetry/exporter-metrics-otlp-http`,
`@opentelemetry/exporter-trace-otlp-http`, `@opentelemetry/instrumentation`,
`@opentelemetry/sdk-logs`, `@opentelemetry/sdk-metrics` (and any related
`@opentelemetry` entries), fill in the change type/summary, save and commit the
generated changeset file alongside your package.json changes so the release
tooling will pick up the modification to `@trigger.dev/core`.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check the actual state of the file
cat -n packages/core/package.json | sed -n '175,190p'

Repository: triggerdotdev/trigger.dev

Length of output: 895


🏁 Script executed:

# Search for changesets in the repository
fd -t f 'changeset' packages/core/

Repository: triggerdotdev/trigger.dev

Length of output: 51


🌐 Web query:

@opentelemetry/host-metrics 0.36.0 peer dependencies npm

💡 Result:

For @opentelemetry/host-metrics@0.36.0, the npm peer dependency is:

  • @opentelemetry/api: ^1.3.0 [1]

🌐 Web query:

@opentelemetry/host-metrics 0.36 sdk-metrics 2.0 compatibility support

💡 Result:

@opentelemetry/host-metrics@0.36.0 is an experimental (0.x) package from opentelemetry-js-contrib and is expected to pair with the stable SDK 1.10.x line (compatibility matrix: stable 1.10.x ↔ experimental 0.36.x). It is not in the expected-compatible set for @opentelemetry/sdk-metrics@2.0.x, which pairs with experimental 0.200.x packages. [1]

Also, as of the npm package metadata, @opentelemetry/host-metrics’ latest published version is still 0.36.0, not a 0.200.x-aligned release. [2]

So: No—@opentelemetry/host-metrics@0.36.0 is not considered compatible with @opentelemetry/sdk-metrics@2.0.x. Use a 1.10.x-era @opentelemetry/sdk-metrics with host-metrics 0.36.x, or wait for/upgrade to a 0.200.x-era host-metrics if/when it’s released. [1][3]

Sources:
[1] OpenTelemetry JS “Package Version Compatibility” matrix (opentelemetry-js repo)
[2] npm package page for @opentelemetry/host-metrics (shows 0.36.0 as current)
[3] OpenTelemetry blog: JS SDK 2.0 uses stable >=2.0.0 with unstable >=0.200.0 packages


🏁 Script executed:

# Also check if there's a CHANGELOG or package.json for host-metrics to understand sdk-metrics dependency
# And verify the full state of the changeset requirement
git log --oneline --all -S "@opentelemetry/host-metrics" | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 188


Replace @opentelemetry/host-metrics@^0.36.0 with version 0.200.x or later to match SDK 2.0.x compatibility.

@opentelemetry/host-metrics@0.36.0 is incompatible with @opentelemetry/sdk-metrics@2.0.1. The 0.36.x line is designed for SDK 1.10.x; SDK 2.0.x requires the 0.200.x unstable package line. Either upgrade host-metrics to 0.200.x (once released) or downgrade the SDK to 1.10.x. Additionally, pin the host-metrics version to an exact number (e.g., 0.200.0 or the compatible 0.36.0 if reverting the SDK) for consistency with other OTel dependencies, which are all pinned exact.

🤖 Prompt for AI Agents
In `@packages/core/package.json` around lines 179 - 185, Update the
`@opentelemetry/host-metrics` dependency to a 0.200.x (unstable 2.0 line) release
pinned to an exact version to match the `@opentelemetry/sdk-metrics`@2.0.1
compatibility; specifically replace "@opentelemetry/host-metrics": "^0.36.0"
with an exact 0.200.x version (e.g., "0.200.0") in package.json so the
host-metrics package line aligns with the SDK 2.0.x packages, or alternatively
revert `@opentelemetry/sdk-metrics` back to the 1.10.x line if you prefer to keep
host-metrics@0.36.0.

NULL_SENTINEL,
} from "./utils/flattenAttributes.js";
export { omit } from "./utils/omit.js";
export { generateFriendlyId, fromFriendlyId } from "./isomorphic/friendlyId.js";
Copy link
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:

#!/bin/bash
# Check for changesets in the .changeset directory
echo "=== Checking for changesets ==="
find .changeset -name "*.md" -type f 2>/dev/null | head -20
echo ""
echo "=== Looking for changesets mentioning core or friendlyId ==="
fd -e md . .changeset/ 2>/dev/null --exec grep -l -i "friendlyId\|core\|generateFriendlyId" {} \; 2>/dev/null || echo "No matching changesets found"

Repository: triggerdotdev/trigger.dev

Length of output: 658


🏁 Script executed:

#!/bin/bash
# Verify the friendlyId.js file exists and contains the exports
echo "=== Checking friendlyId.js file ==="
cat packages/core/src/v3/isomorphic/friendlyId.js 2>/dev/null | head -50

Repository: triggerdotdev/trigger.dev

Length of output: 104


🏁 Script executed:

#!/bin/bash
# Check if these functions were previously exported from v3/index.ts
echo "=== Checking git history for this line ==="
cd packages/core/src/v3 && git log -p --follow -S "generateFriendlyId" -- index.ts 2>/dev/null | head -100 || echo "Git history check failed or not available"

Repository: triggerdotdev/trigger.dev

Length of output: 111


🏁 Script executed:

#!/bin/bash
# Check if the file exists at the expected path
echo "=== Checking if friendlyId.js exists ==="
ls -la packages/core/src/v3/isomorphic/ 2>/dev/null || echo "isomorphic directory not found"

Repository: triggerdotdev/trigger.dev

Length of output: 721


🏁 Script executed:

#!/bin/bash
# Check the actual v3/index.ts file
echo "=== Checking v3/index.ts ==="
cat -n packages/core/src/v3/index.ts 2>/dev/null | grep -A 2 -B 2 "friendlyId" || echo "No friendlyId exports found"

Repository: triggerdotdev/trigger.dev

Length of output: 328


🏁 Script executed:

#!/bin/bash
# List all changesets to see their contents
echo "=== Changesets content ==="
for file in .changeset/*.md; do
  if [ "$file" != ".changeset/README.md" ]; then
    echo "=== $file ==="
    cat "$file"
    echo ""
  fi
done | head -200

Repository: triggerdotdev/trigger.dev

Length of output: 2252


🏁 Script executed:

#!/bin/bash
# Check the friendlyId.ts file to confirm the functions exist
echo "=== Checking friendlyId.ts exports ==="
cat packages/core/src/v3/isomorphic/friendlyId.ts | head -50

Repository: triggerdotdev/trigger.dev

Length of output: 1370


🏁 Script executed:

#!/bin/bash
# Check if there's a changeset that covers this specific change but uses different naming
echo "=== Checking all changesets for 'friendlyId' case-insensitive ==="
grep -r -i "friendlyid\|generateFriendlyId\|fromFriendlyId" .changeset/ 2>/dev/null || echo "No matches found in changesets"

Repository: triggerdotdev/trigger.dev

Length of output: 165


🏁 Script executed:

#!/bin/bash
# Verify this is a new addition - check git diff or status
echo "=== Check git status for changes ==="
git status --short packages/core/src/v3/index.ts 2>/dev/null || echo "Not available"
echo ""
echo "=== Check git diff for this file ==="
git diff HEAD packages/core/src/v3/index.ts 2>/dev/null | head -30 || echo "Not available"

Repository: triggerdotdev/trigger.dev

Length of output: 143


New public API exports — ensure a changeset is included.

Exporting generateFriendlyId and fromFriendlyId from @trigger.dev/core/v3 expands the public API surface. As per coding guidelines, modifications to public packages in packages/* require a changeset via pnpm run changeset:add.

🤖 Prompt for AI Agents
In `@packages/core/src/v3/index.ts` at line 52, You added new public exports
generateFriendlyId and fromFriendlyId (export line in index.ts) which expands
the package API; create and commit a changeset so release tooling and changelogs
capture this public change by running the repository's changeset flow (e.g., run
pnpm run changeset:add), select the appropriate version bump and provide a brief
summary mentioning generateFriendlyId and fromFriendlyId, then commit the
generated changeset file alongside your export change.

Comment on lines 105 to 112
public disable() {
unregisterGlobal(API_NAME);
this._runDisabled = true;
}

public setGlobalTaskContext(taskContext: TaskContext): boolean {
return registerGlobal(API_NAME, taskContext);
this._runDisabled = false;
return registerGlobal(API_NAME, taskContext, true);
}
Copy link
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:

#!/bin/bash
# Find all usages of disable() and isInsideTask to check if any caller
# depends on isInsideTask becoming false after disable()

echo "=== Callers of .disable() on TaskContextAPI ==="
rg -n --type=ts 'taskContext.*\.disable\(\)|\.disable\(\)' -A3 -B1

echo ""
echo "=== Usages of isInsideTask ==="
rg -n --type=ts '\.isInsideTask' -A2 -B1

echo ""
echo "=== Usages of isRunDisabled ==="
rg -n --type=ts '\.isRunDisabled' -A2 -B1

echo ""
echo "=== File: packages/core/src/v3/taskContext/index.ts (context) ==="
sed -n '1,50p' packages/core/src/v3/taskContext/index.ts
sed -n '100,120p' packages/core/src/v3/taskContext/index.ts

Repository: triggerdotdev/trigger.dev

Length of output: 7524


🏁 Script executed:

#!/bin/bash
echo "=== Context around isInsideTask usage in apiClient ==="
sed -n '1420,1450p' packages/core/src/v3/apiClient/index.ts

echo ""
echo "=== Check if isRunDisabled is used in apiClient ==="
rg -n 'isRunDisabled' packages/core/src/v3/apiClient/index.ts -A2 -B2

Repository: triggerdotdev/trigger.dev

Length of output: 1002


Fix apiClient header injection logic to check isRunDisabled.

The behavioral change where disable() no longer unregisters the task context global introduces a bug: after disable() is called, isInsideTask remains true (the context is deliberately preserved between runs), but the run is actually disabled. The apiClient injects worker-specific headers (x-trigger-worker, x-trigger-engine-version) based solely on isInsideTask and will incorrectly inject these headers during disabled states (between runs).

Update the check at packages/core/src/v3/apiClient/index.ts:1431 to also verify that isRunDisabled is false:

if (taskContext.isInsideTask && !taskContext.isRunDisabled) {
  headers["x-trigger-worker"] = "true";
  headers["x-trigger-engine-version"] = "V2";
  // ...
}

This aligns with how other systems (e.g., otelProcessors) already handle the distinction between a registered context and an active run.

🤖 Prompt for AI Agents
In `@packages/core/src/v3/taskContext/index.ts` around lines 105 - 112, The
apiClient header injection currently checks only taskContext.isInsideTask and
therefore still injects worker headers when a run is disabled; update the
conditional in the api client (around the header injection logic that references
taskContext.isInsideTask) to also require taskContext.isRunDisabled === false
(e.g., if (taskContext.isInsideTask && !taskContext.isRunDisabled) { ... }),
ensuring disable() (which sets _runDisabled via disable()) prevents injection;
verify related symbols: disable(), setGlobalTaskContext(), isInsideTask,
isRunDisabled, and the header keys "x-trigger-worker"/"x-trigger-engine-version"
are guarded by the new condition.

Comment on lines +143 to +173
if (taskContext.isRunDisabled) {
// Between runs: keep environment/project/org/machine attrs, strip run-specific ones
contextAttrs = {
[SemanticInternalAttributes.ENVIRONMENT_ID]: ctx.environment.id,
[SemanticInternalAttributes.ENVIRONMENT_TYPE]: ctx.environment.type,
[SemanticInternalAttributes.ORGANIZATION_ID]: ctx.organization.id,
[SemanticInternalAttributes.PROJECT_ID]: ctx.project.id,
[SemanticInternalAttributes.MACHINE_PRESET_NAME]: ctx.machine?.name,
};
} else {
// During a run: full context attrs
contextAttrs = {
[SemanticInternalAttributes.RUN_ID]: ctx.run.id,
[SemanticInternalAttributes.TASK_SLUG]: ctx.task.id,
[SemanticInternalAttributes.ATTEMPT_NUMBER]: ctx.attempt.number,
[SemanticInternalAttributes.ENVIRONMENT_ID]: ctx.environment.id,
[SemanticInternalAttributes.ORGANIZATION_ID]: ctx.organization.id,
[SemanticInternalAttributes.PROJECT_ID]: ctx.project.id,
[SemanticInternalAttributes.MACHINE_PRESET_NAME]: ctx.machine?.name,
[SemanticInternalAttributes.ENVIRONMENT_TYPE]: ctx.environment.type,
};
}

if (taskContext.worker) {
contextAttrs[SemanticInternalAttributes.WORKER_ID] = taskContext.worker.id;
contextAttrs[SemanticInternalAttributes.WORKER_VERSION] = taskContext.worker.version;
}

if (!taskContext.isRunDisabled && ctx.run.tags?.length) {
contextAttrs[SemanticInternalAttributes.RUN_TAGS] = ctx.run.tags;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

High-cardinality attributes injected into every metric data point.

The coding guidelines explicitly prohibit using UUIDs/IDs (envId, runId, projectId, organizationId), unbounded integers (attemptNumber), and free-form strings (taskSlug, runTags) as OTEL metric attributes because they cause cardinality explosion in Prometheus/Grafana.

Attributes that violate the guideline:

  • ENVIRONMENT_ID, ORGANIZATION_ID, PROJECT_ID, RUN_ID, WORKER_ID — all IDs
  • TASK_SLUG — unbounded string
  • ATTEMPT_NUMBER — unbounded integer
  • RUN_TAGS — free-form string array

Only ENVIRONMENT_TYPE and MACHINE_PRESET_NAME appear to be low-cardinality.

Consider moving the high-cardinality identifiers to resource attributes (set once on the Resource object) rather than per-data-point attributes if they are needed for correlation, or drop them from metrics entirely and rely on trace/log correlation for drill-down. If these are intentionally scoped to internal infrastructure metrics (not user-facing), document that exception clearly.

As per coding guidelines: "Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)." Also based on learnings about OTEL metric attribute cardinality.

🤖 Prompt for AI Agents
In `@packages/core/src/v3/taskContext/otelProcessors.ts` around lines 143 - 173,
The code is injecting high-cardinality attributes into metric data via
contextAttrs (in the block using taskContext.isRunDisabled and the else branch)
which adds IDs and free-form values; remove ENVIRONMENT_ID, ORGANIZATION_ID,
PROJECT_ID, RUN_ID, WORKER_ID, TASK_SLUG, ATTEMPT_NUMBER, and RUN_TAGS from the
per-data-point metric attributes set on contextAttrs and instead either: 1) move
any needed identifiers to the Resource once (use the Resource API when
initializing telemetry) for correlation, or 2) drop them entirely and only keep
low-cardinality attributes (ENVIRONMENT_TYPE and MACHINE_PRESET_NAME) in
contextAttrs; update the logic that conditionally adds RUN_TAGS and worker
fields (the if (!taskContext.isRunDisabled && ctx.run.tags?.length) and the if
(taskContext.worker) blocks) to avoid attaching high-cardinality values to
metrics.

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: 1

🤖 Fix all issues with AI agents
In `@packages/core/src/v3/otel/tracingSDK.ts`:
- Around line 77-92: The TaskContextMetricExporter is currently appending
high-cardinality attributes (RUN_ID, ORGANIZATION_ID, PROJECT_ID,
ENVIRONMENT_ID, WORKER_ID, RUN_TAGS, TASK_SLUG) to all metrics which must be
avoided; update the code so inclusion of these task-context attributes is opt-in
and/or sanitized: add a boolean config flag (e.g.,
includeTaskContextAttributes?: boolean) to TracingSDKConfig and use it where
TaskContextMetricExporter is instantiated (the instantiation referenced as
TaskContextMetricExporter) to skip adding attributes by default, or
alternatively change TaskContextMetricExporter to filter out UUIDs and free-form
string arrays (or hash them) before attaching attributes to metrics; ensure the
new config flag is honored in tracing SDK initialization and documented.
🧹 Nitpick comments (9)
apps/webapp/app/v3/querySchemas.ts (1)

574-583: environment_type uses plain String instead of LowCardinality(String).

The runsSchema defines environment_type with LowCardinality(String) (Line 73), but metricsSchema uses String. Since environment_type is a bounded enum (ENVIRONMENT_TYPES), using LowCardinality(String) would be consistent and may improve ClickHouse query performance when filtering/grouping by this column. The same applies to machine_name at Line 565 which also uses a bounded set (MACHINE_PRESETS) but is defined as String.

internal-packages/tsql/src/query/time_buckets.ts (1)

12-17: Same guideline applies to TimeBucketInterval.

TimeBucketInterval is also defined as an interface but should be a type per coding guidelines. This pre-dates this PR but is worth fixing alongside BucketThreshold.

As per coding guidelines, **/*.{ts,tsx}: Use types over interfaces for TypeScript.

internal-packages/tsql/src/query/printer.ts (1)

743-749: Type-unsafe smuggling of format through Partial<ColumnSchema>.

The format property is not part of the ColumnSchema type but is being attached to sourceColumn in analyzeSelectColumn (Line 984) and then retrieved here via an explicit cast. This works at runtime but undermines type safety.

Consider defining a local type for the analysis result that includes the optional format field, or extending the return type of analyzeSelectColumn to include format directly rather than overloading sourceColumn.

Suggested approach
 private analyzeSelectColumn(col: Expression): {
   outputName: string | null;
   sourceColumn: Partial<ColumnSchema> | null;
   inferredType: ClickHouseType | null;
+  format?: ColumnFormatType;
 } {

Then in the metadata collection:

-      const sourceWithFormat = sourceColumn as (Partial<ColumnSchema> & { format?: ColumnFormatType }) | null;
-      if (sourceWithFormat?.format) {
-        metadata.format = sourceWithFormat.format;
-      } else if (sourceColumn?.customRenderType) {
+      const { format } = this.analyzeSelectColumn(col); // already computed earlier
+      if (format) {
+        metadata.format = format;
+      } else if (sourceColumn?.customRenderType) {
         metadata.format = sourceColumn.customRenderType as ColumnFormatType;
       }

Note: since analyzeSelectColumn is already called at Line 640, you'd store the full result including format rather than calling it twice.

apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (1)

966-976: EXPORT_TIMEOUT set equal to EXPORT_INTERVAL — is this intentional?

Both dev (lines 966-976) and prod (lines 1117-1127) set TRIGGER_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS to the same value as TRIGGER_OTEL_METRICS_EXPORT_INTERVAL_MILLIS. Typically the export timeout should be less than the export interval; if an export takes the full interval, the next collection cycle is immediately due, which can cause back-pressure. If intentional, a brief comment would help future readers.

apps/webapp/app/v3/otlpExporter.server.ts (2)

74-95: No error handling for ClickHouse insert failures in exportMetrics.

If clickhouseClient.metrics.insert(rows) throws, the error propagates up and the client receives no partial-success feedback. The traces/logs paths delegate to #exportEvents which has its own repository layer. Consider whether a try/catch with logging is needed here to avoid dropping the entire batch on transient ClickHouse errors, or whether the caller handles retries.


1140-1143: Pre-existing: process.env accessed directly instead of via env export.

Lines 1140-1143 read process.env.OTLP_EXPORTER_VERBOSE and process.env.SERVER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT directly. This violates the coding guideline requiring all environment variable access through the env export from env.server.ts. Not introduced by this PR, but worth noting since you're touching this file. As per coding guidelines, Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly.

packages/cli-v3/src/dev/taskRunProcessPool.ts (2)

109-119: Consider reusing an existing machine ID for processes of the same version.

Each new process gets a fresh generateFriendlyId("machine"), even within the same dev session. This means each process restart produces a distinct TRIGGER_MACHINE_ID resource attribute in metrics. If the intent is to identify the dev machine (not the individual OS process), consider generating the ID once at pool construction and reusing it.

If the intent is per-process identity, then this is correct as-is.


44-44: Static analysis false positive — clearIdleTimer returns void.

Biome flags this forEach callback as returning a value, but clearIdleTimer returns void. This is a false positive. You can silence it or, if you prefer, switch to a for...of loop:

Optional: use for...of to appease the linter
-    processesToKill.forEach((process) => this.clearIdleTimer(process));
+    for (const process of processesToKill) {
+      this.clearIdleTimer(process);
+    }
packages/core/src/v3/otel/tracingSDK.ts (1)

293-315: Misleading comment — actual collection interval defaults to 30s, not 10s.

Line 300 comments "PeriodicReader(10s)" but exportIntervalMillis at line 311 is Math.max(collectionIntervalMs, exportTimeoutMillis) which defaults to Math.max(10000, 30000) = 30000. The collectionIntervalMs variable name is also misleading since it doesn't end up being the actual collection interval.

Consider aligning the comment and variable naming with the effective behavior:

Suggested clarification
-    const collectionIntervalMs = parseInt(
-      getEnvVar("TRIGGER_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS") ?? "10000"
-    );
-    const exportIntervalMs = parseInt(
-      getEnvVar("TRIGGER_OTEL_METRICS_EXPORT_INTERVAL_MILLIS") ?? "30000"
-    );
-
-    // Chain: PeriodicReader(10s) → TaskContextMetricExporter → BufferingMetricExporter(30s) → OTLP
+    const desiredCollectionIntervalMs = parseInt(
+      getEnvVar("TRIGGER_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS") ?? "10000"
+    );
+    const exportIntervalMs = parseInt(
+      getEnvVar("TRIGGER_OTEL_METRICS_EXPORT_INTERVAL_MILLIS") ?? "30000"
+    );
+
+    // Chain: PeriodicReader → TaskContextMetricExporter → BufferingMetricExporter → OTLP
+    // Note: effective collection interval = max(desiredCollectionIntervalMs, exportTimeoutMillis)
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa450a6 and f3c9dfb.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • references/hello-world/src/trigger/metrics.ts is excluded by !references/**
📒 Files selected for processing (43)
  • apps/webapp/app/components/code/QueryResultsChart.tsx
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/components/primitives/charts/BigNumberCard.tsx
  • apps/webapp/app/components/primitives/charts/Chart.tsx
  • apps/webapp/app/components/primitives/charts/ChartBar.tsx
  • apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx
  • apps/webapp/app/components/primitives/charts/ChartLine.tsx
  • apps/webapp/app/components/primitives/charts/ChartRoot.tsx
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/AITabContent.tsx
  • apps/webapp/app/routes/otel.v1.metrics.ts
  • apps/webapp/app/services/queryService.server.ts
  • apps/webapp/app/utils/columnFormat.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • internal-packages/clickhouse/schema/016_create_metrics_v1.sql
  • internal-packages/clickhouse/src/index.ts
  • internal-packages/clickhouse/src/metrics.ts
  • internal-packages/otlp-importer/src/index.ts
  • internal-packages/tsql/src/index.ts
  • internal-packages/tsql/src/query/printer.test.ts
  • internal-packages/tsql/src/query/printer.ts
  • internal-packages/tsql/src/query/schema.ts
  • internal-packages/tsql/src/query/time_buckets.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
  • packages/cli-v3/src/entryPoints/managed-run-worker.ts
  • packages/cli-v3/src/entryPoints/managed/taskRunProcessProvider.ts
  • packages/cli-v3/src/executions/taskRunProcess.ts
  • packages/core/package.json
  • packages/core/src/v3/config.ts
  • packages/core/src/v3/index.ts
  • packages/core/src/v3/otel/machineId.ts
  • packages/core/src/v3/otel/nodejsRuntimeMetrics.ts
  • packages/core/src/v3/otel/tracingSDK.ts
  • packages/core/src/v3/schemas/messages.ts
  • packages/core/src/v3/semanticInternalAttributes.ts
  • packages/core/src/v3/taskContext/index.ts
  • packages/core/src/v3/taskContext/otelProcessors.ts
  • packages/core/src/v3/workers/index.ts
  • packages/trigger-sdk/src/v3/otel.ts
🚧 Files skipped from review as they are similar to previous changes (24)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/AITabContent.tsx
  • apps/webapp/app/components/primitives/charts/ChartBar.tsx
  • packages/cli-v3/src/entryPoints/managed/taskRunProcessProvider.ts
  • internal-packages/tsql/src/index.ts
  • packages/trigger-sdk/src/v3/otel.ts
  • apps/webapp/app/components/primitives/charts/ChartLine.tsx
  • internal-packages/clickhouse/src/metrics.ts
  • packages/core/src/v3/workers/index.ts
  • packages/core/src/v3/semanticInternalAttributes.ts
  • packages/core/src/v3/schemas/messages.ts
  • packages/cli-v3/src/executions/taskRunProcess.ts
  • internal-packages/clickhouse/src/index.ts
  • packages/core/package.json
  • apps/webapp/app/components/primitives/charts/BigNumberCard.tsx
  • apps/webapp/app/components/primitives/charts/Chart.tsx
  • apps/webapp/app/utils/columnFormat.ts
  • apps/webapp/app/routes/otel.v1.metrics.ts
  • packages/core/src/v3/otel/nodejsRuntimeMetrics.ts
  • packages/core/src/v3/index.ts
  • internal-packages/clickhouse/schema/016_create_metrics_v1.sql
  • apps/webapp/app/components/primitives/charts/ChartRoot.tsx
  • packages/core/src/v3/config.ts
  • packages/core/src/v3/taskContext/otelProcessors.ts
  • packages/core/src/v3/taskContext/index.ts
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • packages/cli-v3/src/entryPoints/managed-run-worker.ts
  • packages/core/src/v3/otel/machineId.ts
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • packages/core/src/v3/otel/tracingSDK.ts
  • internal-packages/tsql/src/query/printer.ts
  • apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx
  • internal-packages/tsql/src/query/time_buckets.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/otlp-importer/src/index.ts
  • internal-packages/tsql/src/query/printer.test.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
  • internal-packages/tsql/src/query/schema.ts
  • apps/webapp/app/components/code/QueryResultsChart.tsx
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • packages/core/src/v3/otel/machineId.ts
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • packages/core/src/v3/otel/tracingSDK.ts
  • apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
  • apps/webapp/app/components/code/QueryResultsChart.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • packages/cli-v3/src/entryPoints/managed-run-worker.ts
  • packages/core/src/v3/otel/machineId.ts
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • packages/core/src/v3/otel/tracingSDK.ts
  • internal-packages/tsql/src/query/printer.ts
  • apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx
  • internal-packages/tsql/src/query/time_buckets.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/otlp-importer/src/index.ts
  • internal-packages/tsql/src/query/printer.test.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
  • internal-packages/tsql/src/query/schema.ts
  • apps/webapp/app/components/code/QueryResultsChart.tsx
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
  • apps/webapp/app/components/code/QueryResultsChart.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
  • apps/webapp/app/components/code/QueryResultsChart.tsx
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • packages/cli-v3/src/entryPoints/managed-run-worker.ts
  • packages/core/src/v3/otel/machineId.ts
  • packages/core/src/v3/otel/tracingSDK.ts
  • internal-packages/tsql/src/query/printer.ts
  • internal-packages/tsql/src/query/time_buckets.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/otlp-importer/src/index.ts
  • internal-packages/tsql/src/query/printer.test.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
  • internal-packages/tsql/src/query/schema.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • packages/cli-v3/src/entryPoints/managed-run-worker.ts
  • packages/core/src/v3/otel/machineId.ts
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • packages/core/src/v3/otel/tracingSDK.ts
  • internal-packages/tsql/src/query/printer.ts
  • apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx
  • internal-packages/tsql/src/query/time_buckets.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/otlp-importer/src/index.ts
  • internal-packages/tsql/src/query/printer.test.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
  • internal-packages/tsql/src/query/schema.ts
  • apps/webapp/app/components/code/QueryResultsChart.tsx
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
{packages,integrations}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Add a changeset when modifying any public package in packages/* or integrations/* using pnpm run changeset:add

Files:

  • packages/cli-v3/src/entryPoints/managed-run-worker.ts
  • packages/core/src/v3/otel/machineId.ts
  • packages/core/src/v3/otel/tracingSDK.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
apps/webapp/app/v3/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Organize services in the webapp following the pattern app/v3/services/*/*.server.ts

Files:

  • apps/webapp/app/v3/services/aiQueryService.server.ts
apps/webapp/app/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts (testable service) and realtimeClientGlobal.server.ts (configuration) in the webapp

Files:

  • apps/webapp/app/services/queryService.server.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

Files:

  • internal-packages/tsql/src/query/printer.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptive describe and it blocks
Tests should avoid mocks or stubs and use the helpers from @internal/testcontainers when Redis or Postgres are needed
Use vitest for running unit tests

**/*.test.{ts,tsx,js,jsx}: Use vitest exclusively for testing and never mock anything - use testcontainers instead
Place test files next to source files with naming pattern: source file (e.g., MyService.ts) → MyService.test.ts

Files:

  • internal-packages/tsql/src/query/printer.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use testcontainers helpers (redisTest, postgresTest, containerTest) from @internal/testcontainers for Redis/PostgreSQL testing instead of mocks

Files:

  • internal-packages/tsql/src/query/printer.test.ts
🧠 Learnings (35)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure OpenTelemetry instrumentations and exporters in trigger.config.ts for enhanced logging
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure OpenTelemetry instrumentations and exporters in trigger.config.ts for enhanced logging

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • packages/cli-v3/src/entryPoints/managed-run-worker.ts
  • packages/core/src/v3/otel/tracingSDK.ts
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/otlp-importer/src/index.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Access environment variables via `env` export from `apps/webapp/app/env.server.ts`, never use `process.env` directly

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
📚 Learning: 2025-11-10T09:09:07.399Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2663
File: apps/webapp/app/env.server.ts:1205-1206
Timestamp: 2025-11-10T09:09:07.399Z
Learning: In the trigger.dev webapp, S2_ACCESS_TOKEN and S2_DEPLOYMENT_LOGS_BASIN_NAME environment variables must remain optional until an OSS version of S2 is available, to avoid breaking environments that don't have S2 provisioned.

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
📚 Learning: 2025-08-19T09:49:07.011Z
Learnt from: julienvanbeveren
Repo: triggerdotdev/trigger.dev PR: 2417
File: apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts:56-61
Timestamp: 2025-08-19T09:49:07.011Z
Learning: In the Trigger.dev codebase, environment variables should default to `isSecret: false` when not explicitly marked as secrets in the syncEnvVars functionality. This is the intended behavior for both regular variables and parent variables.

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
📚 Learning: 2026-01-08T15:57:09.323Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • packages/core/src/v3/otel/tracingSDK.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
📚 Learning: 2026-01-08T15:57:09.323Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/env.server.ts
  • packages/cli-v3/src/entryPoints/managed-run-worker.ts
  • packages/core/src/v3/otel/tracingSDK.ts
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/otlp-importer/src/index.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2026-01-08T15:57:09.323Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs

Applied to files:

  • apps/webapp/app/env.server.ts
  • internal-packages/tsql/src/query/time_buckets.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata methods (set, del, replace, append, remove, increment, decrement, stream, flush) to update metadata during task execution

Applied to files:

  • packages/cli-v3/src/entryPoints/managed-run-worker.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks

Applied to files:

  • packages/cli-v3/src/entryPoints/managed-run-worker.ts
  • packages/core/src/v3/otel/tracingSDK.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Set default machine configuration in trigger.config.ts using `defaultMachine` property

Applied to files:

  • packages/core/src/v3/otel/machineId.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Specify machine resources using the `machine` property with preset options like 'large-1x'

Applied to files:

  • packages/core/src/v3/otel/machineId.ts
  • packages/core/src/v3/otel/tracingSDK.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export tasks with unique IDs within the project to enable proper task discovery and execution

Applied to files:

  • packages/core/src/v3/otel/machineId.ts
  • packages/core/src/v3/otel/tracingSDK.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx} : Every Trigger.dev task must be exported and have a unique `id` property with no timeouts in the run function

Applied to files:

  • packages/core/src/v3/otel/machineId.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `idempotencyKeys.create()` to create idempotency keys for preventing duplicate task executions

Applied to files:

  • packages/core/src/v3/otel/machineId.ts
  • packages/cli-v3/src/dev/taskRunProcessPool.ts
📚 Learning: 2026-02-11T16:37:32.429Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/components/primitives/charts/Card.tsx:26-30
Timestamp: 2026-02-11T16:37:32.429Z
Learning: In projects using react-grid-layout, avoid relying on drag-handle class to imply draggability. Ensure drag-handle elements only affect dragging when the parent grid item is configured draggable in the layout; conditionally apply cursor styles based on the draggable prop. This improves correctness and accessibility.

Applied to files:

  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx
  • apps/webapp/app/components/code/QueryResultsChart.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Attach metadata to task runs using the metadata option when triggering, and access/update it inside runs using metadata functions

Applied to files:

  • packages/core/src/v3/otel/tracingSDK.ts
  • apps/webapp/app/v3/querySchemas.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{ts,tsx} : Use types over interfaces for TypeScript

Applied to files:

  • internal-packages/tsql/src/query/time_buckets.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{ts,tsx} : Avoid using enums; prefer string unions or const objects instead

Applied to files:

  • internal-packages/tsql/src/query/time_buckets.ts
📚 Learning: 2026-01-28T14:15:15.011Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2953
File: apps/webapp/app/components/runs/v3/SharedFilters.tsx:441-452
Timestamp: 2026-01-28T14:15:15.011Z
Learning: In apps/webapp/app/components/runs/v3/SharedFilters.tsx, the maxPeriodDays limit for date ranges should only check the from date (via dateRangeToDays(fromValue)) because it enforces data retention limits—how far back in history queries can reach. The to date is irrelevant for retention-based limits.

Applied to files:

  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/app/services/queryService.server.ts
📚 Learning: 2025-06-10T09:31:01.040Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2158
File: internal-packages/clickhouse/src/client/queryBuilder.ts:77-92
Timestamp: 2025-06-10T09:31:01.040Z
Learning: ClickHouse SQL syntax requires GROUP BY clause to come before ORDER BY clause, following standard SQL clause ordering: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT.

Applied to files:

  • apps/webapp/app/v3/services/aiQueryService.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to internal-packages/clickhouse/schema/**/*.sql : Follow ClickHouse naming conventions: `raw_` prefix for input tables, `_v1`, `_v2` suffixes for versioning, `_mv_v1` suffix for materialized views

Applied to files:

  • apps/webapp/app/v3/services/aiQueryService.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : When editing the Prisma schema, remove extraneous migration lines related to specific tables: `_BackgroundWorkerToBackgroundWorkerFile`, `_BackgroundWorkerToTaskQueue`, `_TaskRunToTaskRunTag`, `_WaitpointRunConnections`, `_completedWaitpoints`, `SecretStore_key_idx`, and unrelated `TaskRun` indexes

Applied to files:

  • apps/webapp/app/services/queryService.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties

Applied to files:

  • packages/cli-v3/src/dev/taskRunProcessPool.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `tasks.trigger()` with type-only imports to trigger tasks from backend code without importing the task implementation

Applied to files:

  • packages/cli-v3/src/dev/taskRunProcessPool.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx} : Always import tasks from `trigger.dev/sdk`, never use `trigger.dev/sdk/v3` or deprecated `client.defineJob` pattern

Applied to files:

  • packages/cli-v3/src/dev/taskRunProcessPool.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2025-06-04T16:02:22.957Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2150
File: apps/supervisor/src/workloadManager/docker.ts:115-115
Timestamp: 2025-06-04T16:02:22.957Z
Learning: In the Trigger.dev codebase, the supervisor component uses DOCKER_ENFORCE_MACHINE_PRESETS while the docker provider component uses ENFORCE_MACHINE_PRESETS. These are separate components with separate environment variable configurations for the same logical concept of enforcing machine presets.

Applied to files:

  • packages/cli-v3/src/dev/taskRunProcessPool.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2026-01-08T15:57:09.323Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Reference the schedule engine (internal-packages/schedule-engine/src/engine/index.ts) as a good example of implementing low-cardinality metric attributes

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/otlpExporter.server.ts
📚 Learning: 2025-07-12T18:06:04.133Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/services/runsRepository.server.ts:172-174
Timestamp: 2025-07-12T18:06:04.133Z
Learning: In apps/webapp/app/services/runsRepository.server.ts, the in-memory status filtering after fetching runs from Prisma is intentionally used as a workaround for ClickHouse data delays. This approach is acceptable because the result set is limited to a maximum of 100 runs due to pagination, making the performance impact negligible.

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
🧬 Code graph analysis (11)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (2)
apps/webapp/app/env.server.ts (1)
  • env (1341-1341)
apps/supervisor/src/env.ts (1)
  • env (144-144)
packages/cli-v3/src/entryPoints/managed-run-worker.ts (1)
packages/core/src/v3/otel/tracingSDK.ts (1)
  • TracingDiagnosticLogLevel (68-75)
packages/core/src/v3/otel/machineId.ts (2)
packages/core/src/v3/workers/index.ts (1)
  • getEnvVar (4-4)
packages/core/src/v3/index.ts (1)
  • generateFriendlyId (52-52)
apps/webapp/app/components/code/TSQLResultsTable.tsx (2)
internal-packages/tsql/src/query/schema.ts (1)
  • column (417-429)
apps/webapp/app/utils/columnFormat.ts (3)
  • formatBytes (8-16)
  • formatDecimalBytes (21-29)
  • formatQuantity (34-40)
packages/core/src/v3/otel/tracingSDK.ts (4)
packages/core/src/v3/semanticInternalAttributes.ts (1)
  • SemanticInternalAttributes (1-70)
packages/core/src/v3/otel/machineId.ts (1)
  • machineId (4-4)
packages/core/src/v3/taskContext/otelProcessors.ts (2)
  • BufferingMetricExporter (204-307)
  • TaskContextMetricExporter (118-202)
packages/core/src/v3/otel/nodejsRuntimeMetrics.ts (1)
  • startNodejsRuntimeMetrics (4-64)
internal-packages/tsql/src/query/printer.ts (5)
internal-packages/tsql/src/index.ts (3)
  • ColumnSchema (110-110)
  • ColumnFormatType (112-112)
  • calculateTimeBucketInterval (138-138)
internal-packages/tsql/src/query/schema.ts (2)
  • ColumnSchema (53-254)
  • ColumnFormatType (279-298)
internal-packages/tsql/src/query/ast.ts (2)
  • Call (482-488)
  • Constant (463-466)
internal-packages/tsql/src/query/errors.ts (1)
  • QueryError (43-45)
internal-packages/tsql/src/query/time_buckets.ts (1)
  • calculateTimeBucketInterval (86-100)
apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx (1)
apps/webapp/app/components/primitives/AnimatedNumber.tsx (1)
  • AnimatedNumber (35-71)
internal-packages/tsql/src/query/time_buckets.ts (1)
internal-packages/tsql/src/index.ts (4)
  • BucketThreshold (139-139)
  • TimeBucketInterval (140-140)
  • BUCKET_THRESHOLDS (137-137)
  • calculateTimeBucketInterval (138-138)
apps/webapp/app/v3/querySchemas.ts (3)
internal-packages/tsql/src/index.ts (3)
  • TableSchema (116-116)
  • column (83-83)
  • BucketThreshold (139-139)
internal-packages/tsql/src/query/schema.ts (2)
  • TableSchema (359-402)
  • column (417-429)
internal-packages/tsql/src/query/time_buckets.ts (1)
  • BucketThreshold (22-27)
internal-packages/tsql/src/query/schema.ts (2)
internal-packages/tsql/src/index.ts (2)
  • ColumnFormatType (112-112)
  • BucketThreshold (139-139)
internal-packages/tsql/src/query/time_buckets.ts (1)
  • BucketThreshold (22-27)
packages/cli-v3/src/entryPoints/dev-run-worker.ts (1)
packages/core/src/v3/otel/tracingSDK.ts (1)
  • TracingDiagnosticLogLevel (68-75)
🪛 ast-grep (0.40.5)
apps/webapp/app/services/queryService.server.ts

[warning] 157-157: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\bFROM\\s+${s.name}\\b, "i")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🪛 Biome (2.3.14)
packages/cli-v3/src/dev/taskRunProcessPool.ts

[error] 44-44: This callback passed to forEach() iterable method should not return a value.

Either remove this return or remove the returned value.

(lint/suspicious/useIterableCallbackReturn)

⏰ 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). (28)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (37)
packages/cli-v3/src/entryPoints/dev-run-worker.ts (2)

205-219: LGTM — TracingSDK metrics configuration looks correct.

The metrics options are cleanly wired: metricsUrl from env, metricReaders from user config with a sensible fallback, host and Node.js runtime metrics enabled, and system.* metrics dropped to reduce dev noise. This aligns with the managed-run-worker approach described in the PR.


628-633: LGTM — disableContext flag in FLUSH handler.

The flush-then-disable ordering is correct: metrics/traces are flushed with the current run context before it's cleared, preventing stale context from tagging subsequent metrics in warm workers.

apps/webapp/app/services/queryService.server.ts (1)

183-192: Cleaner time fallback construction.

The renamed timeFallback path reads clearer and keeps the control flow straightforward.

apps/webapp/app/v3/services/aiQueryService.server.ts (3)

68-68: Time-filter tool guidance is clear and consistent.


369-510: Prompt updates for runs vs. metrics and formatting look solid.


517-619: Edit-mode prompt parity with generation mode is well aligned.

internal-packages/tsql/src/query/schema.ts (1)

273-401: Format hints and per-table bucket thresholds are well modeled.

apps/webapp/app/components/code/TSQLResultsTable.tsx (4)

70-119: Format-hint aware value formatting is consistent for search indexing.


247-261: Display-length heuristics for new format hints look good.


434-437: Right-alignment now correctly includes formatted numeric types.


520-544: Cell rendering honors new format hints cleanly.

internal-packages/tsql/src/query/printer.test.ts (4)

5-11: Import updates align with the new schema/time-buckets types.


2344-2357: Metadata expectations for format propagation are well asserted.


2699-2825: Comprehensive prettyFormat() test coverage—nice work.


3710-3777: Per-table timeBucketThresholds tests cover the key branch logic.

apps/webapp/app/v3/querySchemas.ts (1)

601-612: Time bucket thresholds look well-designed for pre-aggregated 10s metric buckets.

The progressive threshold ladder from 10s buckets up to 1-week intervals is appropriate for the metrics table's pre-aggregated data model. The satisfies BucketThreshold[] annotation ensures type safety at compile time.

apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx (1)

29-30: Clean integration of valueFormatter prop throughout the legend component.

The optional valueFormatter is correctly threaded to all value display sites (header total, per-item totals, and the hovered hidden item row), with a consistent fallback to AnimatedNumber. The repeated ternary could be extracted into a small helper for DRY, but the current form is readable and the scope is contained.

Also applies to: 55-55, 185-189, 261-265, 329-329, 354-359

internal-packages/tsql/src/query/time_buckets.ts (1)

86-97: Optional thresholds parameter with default fallback is clean.

The calculateTimeBucketInterval correctly falls back to BUCKET_THRESHOLDS when no custom thresholds are provided, enabling per-table override without breaking existing callers.

apps/webapp/app/components/code/QueryResultsChart.tsx (2)

804-821: Format resolution and formatter plumbing look correct.

The yAxisFormat cascading from col?.format to col?.customRenderType correctly prioritizes explicit format hints (from prettyFormat()) over schema-level render types. The tooltipValueFormatter and yAxisFormatter are appropriately memoized.


1092-1096: createYAxisFormatter signature updated cleanly with optional format parameter.

Backward-compatible change — existing callers without a format hint will get the default numeric formatter.

internal-packages/tsql/src/query/printer.ts (3)

944-989: prettyFormat() analysis is well-structured with proper validation.

The implementation correctly validates argument count and format type against a whitelist, recursively analyzes the inner expression, and augments the source column with the format hint. The separation between analysis (metadata enrichment) and emission (stripping the wrapper) is clean.


2861-2867: prettyFormat() correctly stripped during SQL emission.

The visitCall handler emits only the inner expression, ensuring no prettyFormat function leaks into the generated ClickHouse SQL. This cleanly separates the metadata-only concern from SQL generation.


3045-3050: timeBucket() correctly uses per-table thresholds.

The integration passes tableSchema.timeBucketThresholds to calculateTimeBucketInterval, enabling the metrics table to use its coarser thresholds (10s minimum) while the runs table continues using the default thresholds.

internal-packages/otlp-importer/src/index.ts (1)

13-17: LGTM!

The new metrics type exports follow the same structure as the existing trace and log exports.

Also applies to: 42-56, 82-98

apps/webapp/app/v3/otlpExporter.server.ts (2)

453-459: floorToTenSecondBuckettoISOString() produces UTC which is correct for ClickHouse, but verify the regex.

The regex /\.\d{3}Z$/ strips the milliseconds and trailing Z. This works for standard ISO output (2024-01-01T00:00:00.000Z) but would silently leave trailing characters if toISOString() ever returns sub-millisecond precision. This is safe in practice for V8's Date.prototype.toISOString(), which always returns exactly three fractional digits.


496-511: No actionable issue found. Gauge rows correctly use count: 0 because they represent instantaneous measurements with no aggregation semantics. The metrics_v1 table uses MergeTree() (not AggregatingMergeTree), has no materialized views performing aggregations, and query patterns explicitly recommend using max_value or last_value directly for gauges—never sum_value / count. The count field is informational metadata about data point aggregation, not used in downstream calculations. Histograms that require averaging already pre-calculate last_value = count > 0 ? sum / count : 0 in the exporter with a zero-check, eliminating division-by-zero risk.

Likely an incorrect or invalid review comment.

apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (1)

959-984: Prod metrics endpoint relies on TracingSDK fallback derivation.

While resolveBuiltInProdVariables doesn't explicitly set TRIGGER_OTEL_METRICS_ENDPOINT (unlike dev's DEV_OTEL_METRICS_ENDPOINTTRIGGER_OTEL_METRICS_ENDPOINT mapping), this is intentional. The TracingSDK falls back to deriving the metrics URL as ${TRIGGER_OTEL_EXPORTER_OTLP_ENDPOINT}/v1/metrics when TRIGGER_OTEL_METRICS_ENDPOINT is absent.

If prod deployments need metrics at a different endpoint than the OTLP endpoint's /v1/metrics path, consider adding a PROD_OTEL_METRICS_ENDPOINT variable (mirroring the dev pattern) to allow explicit configuration rather than relying on the derived path.

packages/core/src/v3/otel/machineId.ts (1)

1-4: LGTM — module-level singleton is appropriate for stable machine identity.

The eager evaluation at import time ensures a consistent machine ID across the process lifetime, which is the correct behavior for OTEL resource attributes.

packages/cli-v3/src/entryPoints/managed-run-worker.ts (2)

184-196: LGTM — TracingSDK wired with metrics support.

The metrics configuration (endpoint, readers, host and runtime metrics) is correctly plumbed through to the TracingSDK for managed workers.


614-618: LGTM — disableContext decouples flush from context lifecycle.

This enables warm workers to keep emitting metrics between runs with stripped-down context attributes (via TaskContextMetricExporter's isRunDisabled branch), which is the right approach.

packages/cli-v3/src/dev/taskRunProcessPool.ts (2)

27-28: Idle timer mechanism is well-designed.

The 30-second idle timeout with synchronous pool removal before async kill is a clean approach to prevent resource leaks from unused warm processes in dev mode.


223-254: Race-safety in startIdleTimer is correct.

The synchronous removal from the available pool before the async killProcess call (lines 228–233) prevents getProcess from handing out a process that's about to be killed. Good defensive pattern even in single-threaded JS, since killProcess is async.

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

282-336: Metrics pipeline setup looks solid overall.

The exporter chain (PeriodicReader → TaskContextMetricExporter → BufferingMetricExporter → OTLP) with configurable intervals, the MeterProvider views for dropping unwanted metrics, and conditional host/runtime metrics are all well-structured.


342-356: LGTM — flush and shutdown correctly include the meter provider.

Parallelizing trace, log, and metric provider flush/shutdown with Promise.all is the right approach.


120-134: LGTM — resource attributes include machine identity.

Adding machineId as a resource attribute enables per-machine metric slicing. Using literal strings for "cloud.provider" and "service.name" avoids a dependency on deprecated @opentelemetry/semantic-conventions constants.


550-552: Good backward-compat fallback for HTTP URL attribute.

Checking both "http.url" (old semconv) and "url.full" (new semconv) ensures this works across instrumentation library versions.


317-324: No issues found. The AggregationType.DROP aggregation and views configuration shape are correct for @opentelemetry/sdk-metrics@1.25.1. The implementation matches the official OpenTelemetry JavaScript SDK documentation.

Comment on lines 77 to 92
export type TracingSDKConfig = {
url: string;
metricsUrl?: string;
forceFlushTimeoutMillis?: number;
instrumentations?: Instrumentation[];
exporters?: SpanExporter[];
logExporters?: LogRecordExporter[];
metricReaders?: MetricReader[];
diagLogLevel?: TracingDiagnosticLogLevel;
resource?: Resource;
hostMetrics?: boolean;
/** Enable Node.js runtime metrics (event loop utilization, heap usage, etc.) */
nodejsRuntimeMetrics?: boolean;
/** Metric instrument name patterns to drop (supports wildcards, e.g. "system.cpu.*") */
droppedMetrics?: string[];
};
Copy link
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:

#!/bin/bash
# Find and examine TaskContextMetricExporter
fd "otelProcessors" -t f packages/core/src/v3/ | head -5

Repository: triggerdotdev/trigger.dev

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Look at the specific file mentioned in the comment
cat -n packages/core/src/v3/taskContext/otelProcessors.ts | head -100

Repository: triggerdotdev/trigger.dev

Length of output: 3694


🏁 Script executed:

#!/bin/bash
# Check line 302 and surrounding context in tracingSDK.ts
sed -n '295,310p' packages/core/src/v3/otel/tracingSDK.ts

Repository: triggerdotdev/trigger.dev

Length of output: 713


🏁 Script executed:

#!/bin/bash
# Search for SemanticInternalAttributes usage
rg "SemanticInternalAttributes\." packages/core/src/v3/ -A 2 -B 2 --type ts | head -50

Repository: triggerdotdev/trigger.dev

Length of output: 3848


🏁 Script executed:

#!/bin/bash
# Get the full otelProcessors.ts file
wc -l packages/core/src/v3/taskContext/otelProcessors.ts

Repository: triggerdotdev/trigger.dev

Length of output: 123


🏁 Script executed:

#!/bin/bash
# Get the full file content
cat -n packages/core/src/v3/taskContext/otelProcessors.ts

Repository: triggerdotdev/trigger.dev

Length of output: 12191


🏁 Script executed:

#!/bin/bash
# Also check semanticInternalAttributes for the constants
fd "semanticInternalAttributes" packages/core/src/v3 -t f

Repository: triggerdotdev/trigger.dev

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Check semanticInternalAttributes.ts
cat -n packages/core/src/v3/semanticInternalAttributes.ts | head -100

Repository: triggerdotdev/trigger.dev

Length of output: 2953


🏁 Script executed:

#!/bin/bash
# Check if there's documentation or context about whether this is intentional
rg "TaskContextMetricExporter\|ClickHouse" packages/core/src/v3 --type ts -B 2 -A 2 | head -50

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

#!/bin/bash
# Check what the intended storage backend is
rg "ClickHouse\|cardinality" packages/core/src/v3 --type ts -i | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 51


High-cardinality metric attributes are injected by TaskContextMetricExporter on all exported metrics.

The TaskContextMetricExporter (instantiated at line 302) appends context attributes to every metric data point during export (lines 154–173 in otelProcessors.ts). These attributes include:

  • RUN_ID, ORGANIZATION_ID, PROJECT_ID, ENVIRONMENT_ID (UUIDs)
  • WORKER_ID (UUID, if worker context exists)
  • RUN_TAGS (free-form string array)
  • TASK_SLUG (task identifier string)

Per the coding guidelines, these are explicitly prohibited for OTEL metrics (UUIDs/IDs and free-form strings are high-cardinality). This will cause metric cardinality explosions in Prometheus or other cardinality-sensitive exporters if users configure custom metricReaders. Verify this is an intentional design choice for a specific backend (e.g., ClickHouse) before proceeding.

🤖 Prompt for AI Agents
In `@packages/core/src/v3/otel/tracingSDK.ts` around lines 77 - 92, The
TaskContextMetricExporter is currently appending high-cardinality attributes
(RUN_ID, ORGANIZATION_ID, PROJECT_ID, ENVIRONMENT_ID, WORKER_ID, RUN_TAGS,
TASK_SLUG) to all metrics which must be avoided; update the code so inclusion of
these task-context attributes is opt-in and/or sanitized: add a boolean config
flag (e.g., includeTaskContextAttributes?: boolean) to TracingSDKConfig and use
it where TaskContextMetricExporter is instantiated (the instantiation referenced
as TaskContextMetricExporter) to skip adding attributes by default, or
alternatively change TaskContextMetricExporter to filter out UUIDs and free-form
string arrays (or hash them) before attaching attributes to metrics; ensure the
new config flag is honored in tracing SDK initialization and documented.

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.

🧹 Nitpick comments (5)
internal-packages/tsql/src/query/printer.ts (3)

743-749: The format property is smuggled via a type assertion — consider a typed return.

analyzeSelectColumn attaches format onto sourceColumn (line 984), but ColumnSchema doesn't declare that field. Here you cast to Partial<ColumnSchema> & { format?: ColumnFormatType } to read it back. This coupling is fragile — a refactor of the return type could silently break the flow.

Consider adding format?: ColumnFormatType to analyzeSelectColumn's return type directly (as a sibling of sourceColumn) rather than embedding it inside the column object:

♻️ Suggested approach
 private analyzeSelectColumn(col: Expression): {
   outputName: string | null;
   sourceColumn: Partial<ColumnSchema> | null;
   inferredType: ClickHouseType | null;
+  format?: ColumnFormatType;
 } {

Then in the prettyFormat branch, return format as a top-level field instead of merging it into sourceColumn. The consumer in visitSelectColumnWithMetadata can then read format directly without a cast.


963-977: validFormats list can drift from ColumnFormatType — consider deriving one from the other.

The hardcoded validFormats array must be kept in sync with ColumnFormatType manually. If a new format is added to the union type, forgetting to update this list would silently reject it at runtime.

♻️ Suggested approach
+const PRETTY_FORMAT_TYPES = [
+  "bytes",
+  "decimalBytes",
+  "quantity",
+  "percent",
+  "duration",
+  "durationSeconds",
+  "costInDollars",
+  "cost",
+] as const satisfies readonly ColumnFormatType[];

 // In analyzeSelectColumn:
-const validFormats = [
-  "bytes",
-  "decimalBytes",
-  "quantity",
-  "percent",
-  "duration",
-  "durationSeconds",
-  "costInDollars",
-  "cost",
-];
-if (!validFormats.includes(formatType)) {
+if (!(PRETTY_FORMAT_TYPES as readonly string[]).includes(formatType)) {

Using satisfies readonly ColumnFormatType[] ensures the compiler flags any value not in the union, catching drift at build time.


2868-2874: Format type validation is skipped when prettyFormat() is used outside SELECT.

In visitCall, the wrapper is stripped without validating the format type string. Validation only happens in analyzeSelectColumn (which only runs for SELECT columns). If a user writes WHERE prettyFormat(x, 'bogus') > 5, it silently passes — no error, but also no harm since the wrapper is stripped. This is likely acceptable for a display-only hint, but worth noting for consistency.

apps/webapp/app/v3/querySchemas.ts (2)

575-584: Inconsistent column types for environment_type and machine_name vs. runsSchema.

In runsSchema, environment_type (line 74) and machine (line 395) use LowCardinality(String). Here, both environment_type and machine_name (line 568) use plain String despite having the same bounded allowedValues. While these are expression columns extracted from JSON, using consistent TSQL types improves schema coherence and could help the query engine optimize accordingly.

Suggested fix
     machine_name: {
       name: "machine_name",
-      ...column("String", {
+      ...column("LowCardinality(String)", {
         description: "The machine preset used for execution",
         allowedValues: [...MACHINE_PRESETS],
         example: "small-1x",
       }),
       expression: "attributes.trigger.machine_name",
     },
     environment_type: {
       name: "environment_type",
-      ...column("String", {
+      ...column("LowCardinality(String)", {
         description: "Environment type",
         allowedValues: [...ENVIRONMENT_TYPES],
         customRenderType: "environmentType",
         example: "PRODUCTION",
       }),
       expression: "attributes.trigger.environment_type",
     },

485-492: Rename metric_subject to be more specific or document its constraints.

metric_subject is consistently populated only with machineId values (lines 502, 527, 554 in otlpExporter.server.ts), yet the ClickHouse column name is generically scoped as metric_subject while the TSQL alias is specifically named machine_id. This naming mismatch could confuse developers querying ClickHouse directly or extending the schema later. Either align the ClickHouse column name to machine_id to match the TSQL side, or add a schema comment clarifying that metric_subject is constrained to machine identifiers.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3c9dfb and ed7e891.

📒 Files selected for processing (3)
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/tsql/src/query/printer.ts
  • internal-packages/tsql/src/query/schema.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • internal-packages/tsql/src/query/schema.ts
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/tsql/src/query/printer.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • internal-packages/tsql/src/query/schema.ts
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/tsql/src/query/printer.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • internal-packages/tsql/src/query/schema.ts
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/tsql/src/query/printer.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • internal-packages/tsql/src/query/schema.ts
  • apps/webapp/app/v3/querySchemas.ts
  • internal-packages/tsql/src/query/printer.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/v3/querySchemas.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/v3/querySchemas.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/v3/querySchemas.ts
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure OpenTelemetry instrumentations and exporters in trigger.config.ts for enhanced logging
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : When editing the Prisma schema, remove extraneous migration lines related to specific tables: `_BackgroundWorkerToBackgroundWorkerFile`, `_BackgroundWorkerToTaskQueue`, `_TaskRunToTaskRunTag`, `_WaitpointRunConnections`, `_completedWaitpoints`, `SecretStore_key_idx`, and unrelated `TaskRun` indexes

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Attach metadata to task runs using the metadata option when triggering, and access/update it inside runs using metadata functions

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2026-01-08T15:57:09.323Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2026-01-08T15:57:09.323Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2026-01-08T15:57:09.323Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Reference the schedule engine (internal-packages/schedule-engine/src/engine/index.ts) as a good example of implementing low-cardinality metric attributes

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2026-01-08T15:57:09.323Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx} : Every Trigger.dev task must be exported and have a unique `id` property with no timeouts in the run function

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2025-07-12T18:06:04.133Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/services/runsRepository.server.ts:172-174
Timestamp: 2025-07-12T18:06:04.133Z
Learning: In apps/webapp/app/services/runsRepository.server.ts, the in-memory status filtering after fetching runs from Prisma is intentionally used as a workaround for ClickHouse data delays. This approach is acceptable because the result set is limited to a maximum of 100 runs due to pagination, making the performance impact negligible.

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
🧬 Code graph analysis (3)
internal-packages/tsql/src/query/schema.ts (1)
internal-packages/tsql/src/query/time_buckets.ts (1)
  • BucketThreshold (22-27)
apps/webapp/app/v3/querySchemas.ts (3)
internal-packages/tsql/src/query/schema.ts (2)
  • TableSchema (359-408)
  • column (423-435)
internal-packages/tsql/src/index.ts (3)
  • TableSchema (116-116)
  • column (83-83)
  • BucketThreshold (139-139)
internal-packages/tsql/src/query/time_buckets.ts (1)
  • BucketThreshold (22-27)
internal-packages/tsql/src/query/printer.ts (4)
internal-packages/tsql/src/query/schema.ts (2)
  • ColumnSchema (53-254)
  • ColumnFormatType (279-298)
internal-packages/tsql/src/index.ts (3)
  • ColumnSchema (110-110)
  • ColumnFormatType (112-112)
  • calculateTimeBucketInterval (138-138)
internal-packages/tsql/src/query/ast.ts (3)
  • Call (482-488)
  • Constant (463-466)
  • Field (468-472)
internal-packages/tsql/src/query/time_buckets.ts (1)
  • calculateTimeBucketInterval (86-100)
⏰ 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). (28)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 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 (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: typecheck / typecheck
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
internal-packages/tsql/src/query/schema.ts (2)

273-298: LGTM — ColumnFormatType is well-structured as a string union.

Clean use of a string union type (no enum), good separation of existing render types vs. new format hints, and well-documented.


395-407: LGTM — timeBucketThresholds and useFinal are clean, well-documented additions.

Per-table time bucket overrides and opt-in FINAL for ReplacingMergeTree are sensible schema extensions.

internal-packages/tsql/src/query/printer.ts (3)

1618-1633: LGTM — conditional FINAL based on useFinal is correct.

Good change to make FINAL opt-in per table rather than always applied. The redundant lookupTable call (already called earlier in the same method) is harmless since it's just a map lookup.


3052-3057: LGTM — per-table time bucket thresholds cleanly wired through.


62-62: LGTM — type-only import of ColumnFormatType.

apps/webapp/app/v3/querySchemas.ts (3)

36-36: LGTM — useFinal: true is appropriate for a ReplacingMergeTree table.

This ensures deduplication is applied when querying task_runs_v2.


602-613: Time bucket thresholds are well-structured and appropriate for 10s pre-aggregated data.

The progression is sensible, and the satisfies keyword ensures type safety without losing the literal type. Minor note: with a 30-day TTL on metrics_v1, the thresholds beyond 30 days (90d, 180d, 365d) won't be exercised currently, but they're harmless and provide forward-compatibility.


619-619: LGTM — metricsSchema correctly added to the exported querySchemas array.

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.

1 participant