Skip to content

Conversation

@ericallam
Copy link
Member

@ericallam ericallam commented Jan 17, 2026

Summary

  • Store the original user-provided idempotency key and scope alongside the hash
  • Expose ctx.run.idempotencyKey as the user-provided key (not the hash)
  • Add ctx.run.idempotencyKeyScope to show the scope ("run", "attempt", or "global")
CleanShot 2026-01-19 at 11 40 46

Problem

Idempotency keys were hashed (SHA-256) before storage, making debugging difficult since users couldn't see the value they originally set or search for runs by idempotency key.

Solution

Attach metadata to the String object returned by idempotencyKeys.create() using a Symbol, extract it in the SDK before the API call, and store it in the database alongside the hash.

const key = await idempotencyKeys.create("my-key", { scope: "global" });
await childTask.triggerAndWait(payload, { idempotencyKey: key });

// In child task:
ctx.run.idempotencyKey      // "my-key" (previously showed the hash)
ctx.run.idempotencyKeyScope // "global"

Test plan

  • Trigger task with idempotencyKeys.create() using different scopes (run, attempt, global)
  • Verify ctx.run.idempotencyKey returns user-provided key
  • Verify ctx.run.idempotencyKeyScope returns correct scope
  • Verify PostgreSQL stores idempotencyKeyOptions JSON
  • Verify ClickHouse receives idempotency_key_user and idempotency_key_scope via replication

@changeset-bot
Copy link

changeset-bot bot commented Jan 17, 2026

🦋 Changeset detected

Latest commit: 9b431a0

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

This PR includes changesets to release 27 packages
Name Type
@trigger.dev/sdk Patch
@trigger.dev/core Patch
@trigger.dev/python Patch
d3-chat Patch
references-d3-openai-agents Patch
references-nextjs-realtime Patch
references-realtime-hooks-test Patch
references-realtime-streams Patch
references-telemetry Patch
@trigger.dev/build Patch
trigger.dev Patch
@trigger.dev/redis-worker Patch
@trigger.dev/schema-to-json Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/zod-worker Patch
@trigger.dev/react-hooks Patch
@trigger.dev/rsc Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch

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

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 17, 2026

Walkthrough

Adds end-to-end support for user-provided idempotency key options. New optional TaskRun.idempotencyKeyOptions (JSONB) and ClickHouse columns idempotency_key_user / idempotency_key_scope persist the original key and scope. Core idempotency helpers and types were extended to attach, extract, and propagate options; server-only helpers expose user-provided key and scope. The RunEngine, runAttemptSystem, SDK batching/trigger plumbing, presenters, replication service, and related APIs were updated to accept, forward, and surface idempotencyKeyOptions and idempotency key scope. UI and route changes expose and allow resetting the user-provided key.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: exposing user-provided idempotency key and scope in task context, which aligns with the primary objectives of the PR.
Description check ✅ Passed The description includes a clear summary of changes, explains the problem and solution, provides code examples, and outlines a test plan, though the checklist items are not explicitly marked as completed.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

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

🤖 Fix all issues with AI agents
In `@apps/webapp/app/services/runsReplicationService.server.ts`:
- Around line 894-895: The `#extractIdempotencyKeyScope` method currently returns
whatever is stored in idempotencyKeyOptions without validation; update it to
defensively validate the extracted scope is a string and one of the allowed
values "run" | "attempt" | "global" (matching the check used in
runAttemptSystem.ts) and if not, fall back to a safe default (e.g., "run") or
handle it the same way runAttemptSystem.ts does; locate and modify the
`#extractIdempotencyKeyScope` function to perform this whitelist check before
returning the scope.

In `@internal-packages/clickhouse/src/taskRuns.ts`:
- Around line 96-97: TASK_RUN_COLUMNS has its entries out of order causing
writes to map to wrong ClickHouse columns; update the TASK_RUN_COLUMNS array so
that columns added by migrations are appended in the actual table order: ensure
after the existing columns you place concurrency_key, bulk_action_group_ids,
worker_queue, max_duration_in_seconds, idempotency_key_user,
idempotency_key_scope (in that exact sequence) so the array order matches the
ClickHouse schema and insert mapping is correct.
🧹 Nitpick comments (1)
apps/webapp/app/presenters/v3/SpanPresenter.server.ts (1)

713-726: Consider adding Zod validation for the parsed JSON options.

The type assertion on run.idempotencyKeyOptions assumes the stored JSON matches the expected shape. If the data is malformed (e.g., from a migration issue or schema evolution), this could cause subtle runtime issues when accessing options.key.

As per coding guidelines, Zod should be used for validation in apps/webapp.

♻️ Suggested validation approach
+import { z } from "zod";
+
+const IdempotencyKeyOptionsSchema = z.object({
+  key: z.string().optional(),
+  scope: z.string().optional(),
+}).nullable();
+
 getUserProvidedIdempotencyKey(
   run: Pick<FindRunResult, "idempotencyKey" | "idempotencyKeyOptions">
 ): string | null {
-  const options = run.idempotencyKeyOptions as {
-    key?: string;
-    scope?: string;
-  } | null;
+  const parsed = IdempotencyKeyOptionsSchema.safeParse(run.idempotencyKeyOptions);
+  const options = parsed.success ? parsed.data : null;
   if (options?.key) {
     return options.key;
   }
   return run.idempotencyKey;
 }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72594a4 and b6b0b67.

⛔ Files ignored due to path filters (1)
  • references/hello-world/src/trigger/idempotency.ts is excluded by !references/**
📒 Files selected for processing (15)
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • apps/webapp/app/services/runsReplicationService.server.ts
  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
  • internal-packages/clickhouse/src/taskRuns.ts
  • internal-packages/database/prisma/migrations/20260116154810_add_idempotency_key_options_to_task_run/migration.sql
  • internal-packages/database/prisma/schema.prisma
  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • internal-packages/run-engine/src/engine/types.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • packages/core/src/v3/schemas/api.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/trigger-sdk/src/v3/shared.ts
🧰 Additional context used
📓 Path-based instructions (12)
internal-packages/database/prisma/migrations/**/*.sql

📄 CodeRabbit inference engine (CLAUDE.md)

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
Database indexes must use CONCURRENTLY to avoid table locks and must be in their own separate migration file

Files:

  • internal-packages/database/prisma/migrations/20260116154810_add_idempotency_key_options_to_task_run/migration.sql
**/*.{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/run-engine/src/engine/index.ts
  • apps/webapp/app/services/runsReplicationService.server.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • internal-packages/clickhouse/src/taskRuns.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • packages/trigger-sdk/src/v3/shared.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/run-engine/src/engine/index.ts
  • apps/webapp/app/services/runsReplicationService.server.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • internal-packages/clickhouse/src/taskRuns.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • packages/trigger-sdk/src/v3/shared.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/run-engine/src/engine/index.ts
  • apps/webapp/app/services/runsReplicationService.server.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • internal-packages/clickhouse/src/taskRuns.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • packages/trigger-sdk/src/v3/shared.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • internal-packages/run-engine/src/engine/index.ts
  • apps/webapp/app/services/runsReplicationService.server.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • internal-packages/clickhouse/src/taskRuns.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • packages/trigger-sdk/src/v3/shared.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/services/runsReplicationService.server.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/core/src/v3/idempotencyKeys.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/services/runsReplicationService.server.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.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/runsReplicationService.server.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/services/runsReplicationService.server.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.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/core/src/v3/schemas/api.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • packages/trigger-sdk/src/v3/shared.ts
internal-packages/clickhouse/schema/**/*.sql

📄 CodeRabbit inference engine (CLAUDE.md)

internal-packages/clickhouse/schema/**/*.sql: ClickHouse migrations must use Goose format with -- +goose Up and -- +goose Down markers
Follow ClickHouse naming conventions: raw_ prefix for input tables, _v1, _v2 suffixes for versioning, _mv_v1 suffix for materialized views

Files:

  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
packages/trigger-sdk/**/*.{ts,tsx}

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

In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Files:

  • packages/trigger-sdk/src/v3/shared.ts
🧠 Learnings (25)
📓 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/**/*.{ts,tsx,js,jsx} : Scope idempotency keys globally or to current run using the scope parameter
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
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
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 `idempotencyKeyTTL` option to define a time window during which duplicate triggers return the original run
📚 Learning: 2026-01-15T11:50:06.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.044Z
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:

  • internal-packages/database/prisma/migrations/20260116154810_add_idempotency_key_options_to_task_run/migration.sql
  • internal-packages/database/prisma/schema.prisma
  • internal-packages/clickhouse/src/taskRuns.ts
  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
📚 Learning: 2026-01-15T11:50:06.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.044Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : Database indexes must use CONCURRENTLY to avoid table locks and must be in their own separate migration file

Applied to files:

  • internal-packages/database/prisma/migrations/20260116154810_add_idempotency_key_options_to_task_run/migration.sql
📚 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:

  • internal-packages/run-engine/src/engine/index.ts
  • apps/webapp/app/services/runsReplicationService.server.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • internal-packages/clickhouse/src/taskRuns.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • packages/trigger-sdk/src/v3/shared.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 `idempotencyKeyTTL` option to define a time window during which duplicate triggers return the original run

Applied to files:

  • internal-packages/run-engine/src/engine/index.ts
  • apps/webapp/app/services/runsReplicationService.server.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • internal-packages/database/prisma/schema.prisma
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • packages/trigger-sdk/src/v3/shared.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} : Scope idempotency keys globally or to current run using the scope parameter

Applied to files:

  • internal-packages/run-engine/src/engine/index.ts
  • apps/webapp/app/services/runsReplicationService.server.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • internal-packages/database/prisma/schema.prisma
  • internal-packages/clickhouse/src/taskRuns.ts
  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • packages/trigger-sdk/src/v3/shared.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:

  • internal-packages/run-engine/src/engine/index.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • internal-packages/clickhouse/src/taskRuns.ts
  • packages/core/src/v3/schemas/common.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • packages/trigger-sdk/src/v3/shared.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:

  • internal-packages/run-engine/src/engine/index.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • internal-packages/clickhouse/src/taskRuns.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/trigger-sdk/src/v3/shared.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:

  • internal-packages/run-engine/src/engine/index.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/trigger-sdk/src/v3/shared.ts
📚 Learning: 2026-01-15T11:50:06.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.044Z
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:

  • internal-packages/run-engine/src/engine/index.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • internal-packages/run-engine/src/engine/types.ts
  • packages/core/src/v3/schemas/common.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.batchTrigger()` to trigger multiple runs of a single task with different payloads

Applied to files:

  • internal-packages/run-engine/src/engine/index.ts
  • apps/webapp/app/runEngine/services/triggerTask.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:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • packages/trigger-sdk/src/v3/shared.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:

  • internal-packages/run-engine/src/engine/index.ts
  • packages/core/src/v3/schemas/api.ts
  • packages/trigger-sdk/src/v3/shared.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 `runs.subscribeToRunsWithTag()` to subscribe to all runs with a specific tag

Applied to files:

  • packages/core/src/v3/schemas/api.ts
📚 Learning: 2025-01-13T18:31:48.160Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 1608
File: apps/webapp/app/v3/services/triggerTask.server.ts:418-418
Timestamp: 2025-01-13T18:31:48.160Z
Learning: The `MachinePresetName` schema is used to validate machine preset values in the trigger.dev codebase, ensuring type safety and validation of machine preset options.

Applied to files:

  • packages/core/src/v3/schemas/api.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 `yourTask.trigger()` to trigger a task from inside another task with specified payload

Applied to files:

  • apps/webapp/app/runEngine/services/triggerTask.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 `tasks.trigger()` with type-only imports to trigger tasks from backend code without importing the task implementation

Applied to files:

  • apps/webapp/app/runEngine/services/triggerTask.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.config.ts : Configure OpenTelemetry instrumentations and exporters in trigger.config.ts for enhanced logging

Applied to files:

  • internal-packages/run-engine/src/engine/types.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:

  • internal-packages/run-engine/src/engine/types.ts
📚 Learning: 2026-01-15T11:50:06.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.044Z
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:

  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
📚 Learning: 2026-01-15T11:50:06.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.044Z
Learning: Applies to internal-packages/clickhouse/schema/**/*.sql : ClickHouse migrations must use Goose format with `-- +goose Up` and `-- +goose Down` markers

Applied to files:

  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
📚 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/presenters/v3/ApiRetrieveRunPresenter.server.ts
📚 Learning: 2025-08-14T12:13:20.455Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2392
File: packages/cli-v3/src/utilities/gitMeta.ts:195-218
Timestamp: 2025-08-14T12:13:20.455Z
Learning: In the GitMeta schema (packages/core/src/v3/schemas/common.ts), all fields are intentionally optional to handle partial data from various deployment contexts (local, GitHub Actions, GitHub App). Functions like getGitHubAppMeta() are designed to work with missing environment variables rather than validate their presence.

Applied to files:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.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 packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • packages/trigger-sdk/src/v3/shared.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/trigger-sdk/src/v3/shared.ts
🧬 Code graph analysis (5)
apps/webapp/app/services/runsReplicationService.server.ts (2)
internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts (2)
  • run (1922-1928)
  • run (1930-1939)
packages/core/src/v3/schemas/common.ts (2)
  • TaskRun (209-237)
  • TaskRun (239-239)
apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts (3)
apps/webapp/app/presenters/v3/SpanPresenter.server.ts (4)
  • getUserProvidedIdempotencyKey (713-726)
  • run (608-614)
  • run (616-681)
  • run (683-685)
apps/webapp/app/services/runsReplicationService.server.ts (4)
  • run (846-905)
  • run (907-916)
  • run (957-960)
  • run (962-965)
internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts (2)
  • run (1922-1928)
  • run (1930-1939)
packages/core/src/v3/idempotencyKeys.ts (3)
packages/trigger-sdk/src/v3/idempotencyKeys.ts (1)
  • IdempotencyKey (8-8)
packages/core/src/v3/apiClient/index.ts (1)
  • resetIdempotencyKey (665-680)
packages/core/src/v3/apiClient/core.ts (1)
  • ZodFetchOptions (31-39)
internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts (4)
apps/webapp/app/presenters/v3/SpanPresenter.server.ts (3)
  • run (608-614)
  • run (616-681)
  • run (683-685)
apps/webapp/app/services/runsReplicationService.server.ts (4)
  • run (846-905)
  • run (907-916)
  • run (957-960)
  • run (962-965)
apps/webapp/app/v3/services/completeAttempt.server.ts (1)
  • run (419-517)
internal-packages/run-engine/src/run-queue/index.ts (1)
  • options (1126-1142)
packages/trigger-sdk/src/v3/shared.ts (1)
packages/core/src/v3/idempotencyKeys.ts (2)
  • makeIdempotencyKey (90-104)
  • getIdempotencyKeyOptions (29-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (39)
internal-packages/database/prisma/migrations/20260116154810_add_idempotency_key_options_to_task_run/migration.sql (1)

1-2: LGTM — straightforward nullable JSONB column addition.

No extra indexes or extraneous lines introduced.

internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql (1)

1-16: LGTM — Goose markers and column additions look correct.

Defaults align with non-null String columns.

internal-packages/database/prisma/schema.prisma (1)

592-594: Good addition of idempotencyKeyOptions with clear doc comment.

This aligns with the new persistence layer for user-provided idempotency metadata.

internal-packages/run-engine/src/engine/types.ts (1)

127-128: Nice addition of idempotencyKeyOptions to TriggerParams.

This cleanly exposes the user-provided key and scope in the engine API.

internal-packages/run-engine/src/engine/index.ts (1)

398-399: LGTM — idempotencyKeyOptions is correctly threaded into TaskRun creation.

This ensures the new options are persisted alongside the run.

Also applies to: 548-548

apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts (3)

21-39: LGTM! Helper function correctly extracts user-provided idempotency key.

The implementation is consistent with the pattern used in SpanPresenter.server.ts. One minor observation: SpanPresenter's version returns string | null while this returns string | undefined. Both work correctly but the slight inconsistency could be unified in a future refactor.


61-61: LGTM!

Correctly adds idempotencyKeyOptions to the select object, enabling the helper function to access user-provided key options.


466-466: LGTM!

Correctly uses the helper to prefer user-provided idempotency keys over the stored hash, ensuring API consumers receive the original key they provided.

packages/core/src/v3/schemas/common.ts (2)

217-220: LGTM! Schema additions for idempotency key metadata.

The fields are correctly added as optional for backwards compatibility. The JSDoc comments clearly document the purpose. The scope enum values align with the IdempotencyKeyOptionsSchema defined in api.ts.


380-383: LGTM! Consistent schema extension in V3TaskRun.

The additions mirror the TaskRun schema changes, maintaining consistency across both schemas.

packages/core/src/v3/schemas/api.ts (4)

152-158: Schema design consideration: Both key and scope are required.

The schema requires both key and scope fields. This aligns with the design where idempotencyKeyOptions is only populated when the user explicitly provides both. If partial options should be supported (e.g., key without scope), consider making scope optional with a default.

Based on learnings, the scope parameter is used to "scope idempotency keys globally or to current run." The current required design seems intentional to ensure explicit scoping when user-provided keys are used.


202-203: LGTM!

Correctly adds idempotencyKeyOptions as an optional field to TriggerTaskRequestBody.options, allowing users to provide their original key and scope when triggering tasks.


253-254: LGTM!

Consistently adds idempotencyKeyOptions to BatchTriggerTaskItem.options, maintaining parity with single-task triggers.


360-361: LGTM!

Correctly adds idempotencyKeyOptions to CreateBatchRequestBody for the 2-phase batch API.

internal-packages/clickhouse/src/taskRuns.ts (2)

158-159: LGTM! Type definitions updated consistently.

Both TaskRunFieldTypes and TaskRunInsertArray are updated with the new string fields at the correct positions matching TASK_RUN_COLUMNS.

Also applies to: 291-292


43-44: LGTM! ClickHouse schema extension for idempotency key metadata.

The fields use default("") which aligns with the extraction helpers in runsReplicationService.server.ts that return empty strings when options are missing. The ClickHouse migration (013_add_task_runs_v2_idempotency_key_options.sql) exists with matching column definitions and proper Goose format, and all four schema locations in taskRuns.ts are updated consistently with correct column ordering.

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

307-307: LGTM!

Correctly propagates idempotencyKeyOptions from the request body to the engine's trigger method, enabling the run engine to persist the user-provided key and scope.

internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts (5)

197-197: LGTM!

Correctly adds idempotencyKeyOptions to the Prisma select to enable the helper functions.


265-266: LGTM!

Correctly populates idempotencyKey and idempotencyKeyScope in the TaskRunContext using the new helper methods.


427-427: LGTM!

Correctly includes idempotencyKeyOptions in the update query's select clause.


576-577: LGTM!

Correctly applies the helpers to populate idempotency key fields in the execution context returned to workers.


1921-1939: LGTM! Well-implemented helper methods.

The private helpers follow the same pattern established across the codebase:

  1. #getUserProvidedIdempotencyKey correctly returns user-provided key if available, falling back to the hash
  2. #getIdempotencyKeyScope properly validates that scope is one of the allowed values before returning

The explicit scope validation on lines 1935-1936 is a good defensive check that ensures only valid enum values propagate to consumers.

apps/webapp/app/presenters/v3/SpanPresenter.server.ts (2)

232-232: LGTM!

The refactoring to use getUserProvidedIdempotencyKey properly prioritizes user-provided keys over stored hashes, which aligns with the PR objective.

Also applies to: 648-648


358-358: LGTM!

The addition of idempotencyKeyOptions to the select query ensures the field is available for the helper method.

packages/trigger-sdk/src/v3/shared.ts (10)

14-14: LGTM!

Import of getIdempotencyKeyOptions is correctly added to support the new feature.


2118-2135: LGTM!

The idempotency key processing in trigger_internal correctly extracts options and passes both the stringified key and options to the API.


2065-2094: Inconsistent idempotencyKeyOptions propagation across transform functions.

This function correctly extracts and propagates idempotencyKeyOptions, but the other five similar transform functions do not:

  • transformBatchItemsStream (lines 1791-1835)
  • transformBatchItemsStreamForWait (lines 1843-1887)
  • transformBatchByTaskItemsStream (lines 1894-1938)
  • transformBatchByTaskItemsStreamForWait (lines 1945-1989)
  • transformSingleTaskBatchItemsStream (lines 1996-2042)

This means user-provided idempotency key options won't be preserved when using streaming batches via batchTriggerById, batchTriggerByIdAndWait, batchTriggerTasks, or batchTriggerAndWaitTasks.

Is this intentional, or should the same pattern be applied to all transform functions for consistency?


630-654: Similar inconsistency in array-based batch paths.

The public batchTriggerById, batchTriggerByIdAndWait, batchTriggerTasks, and batchTriggerAndWaitTasks functions don't extract idempotencyKeyOptions from individual items in their array paths, while batchTrigger_internal and batchTriggerAndWait_internal do.

This creates an inconsistent user experience where the same feature works for some batch APIs but not others.

Also applies to: 886-910, 1146-1170, 1407-1430


1544-1545: LGTM!

The executeBatchTwoPhase options type correctly includes idempotencyKeyOptions with the proper scope union type.

Also applies to: 1559-1560


2239-2254: LGTM!

The batch-level idempotency key options are correctly processed and passed to executeBatchTwoPhase for the array path.


2301-2316: LGTM!

The streaming path correctly extracts and passes batch-level idempotency key options to executeBatchTwoPhaseStreaming.


2374-2403: LGTM!

The triggerAndWait_internal correctly processes the idempotency key and extracts options for storage.


2512-2529: LGTM!

The batch-level idempotency key processing in batchTriggerAndWait_internal array path is correct.


2581-2598: LGTM!

The streaming path in batchTriggerAndWait_internal correctly handles batch-level idempotency key options.

packages/core/src/v3/idempotencyKeys.ts (5)

7-12: LGTM!

New types correctly define the idempotency key scope and options structure. Using type instead of interface aligns with coding guidelines.


14-46: LGTM!

The symbol-based metadata attachment pattern is well-implemented:

  • Uses Symbol.for() for consistent cross-realm identity
  • Creates String objects via new String() to allow property attachment while maintaining string semantics
  • Internal helper is properly marked with @internal JSDoc

48-61: LGTM!

The updated isIdempotencyKey correctly handles both primitive strings and String objects created by attachIdempotencyKeyOptions.


141-153: LGTM!

The createIdempotencyKey function now correctly attaches the original user key and scope as metadata, enabling downstream extraction via getIdempotencyKeyOptions.


192-279: LGTM!

The resetIdempotencyKey function handles multiple input formats correctly:

  • Direct 64-char hashes pass through efficiently
  • IdempotencyKey objects with attached options have their metadata extracted
  • Raw strings/arrays are hashed with proper scope handling
  • Clear error messages when required context is missing outside task runs

The documentation is thorough with good examples.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@ericallam
Copy link
Member Author

@claude could you review this PR please?

@claude
Copy link

claude bot commented Jan 17, 2026

Claude finished @ericallam's task in 2m 14s —— View job


PR Review: feat(sdk): expose user-provided idempotency key and scope in task context


Overview

This PR solves an important usability problem: previously, idempotency keys were hashed (SHA-256) before storage, making debugging difficult since users couldn't see the value they originally set. The solution elegantly stores the original key and scope alongside the hash, exposing them via ctx.run.idempotencyKey and ctx.run.idempotencyKeyScope.


Architecture Assessment ✅

The implementation approach is well-designed:

  1. Symbol-based metadata attachment (packages/core/src/v3/idempotencyKeys.ts:14-46): Using Symbol.for() to attach metadata to String objects is a clever pattern that:

    • Maintains backward compatibility (the string still works as a string)
    • Allows extraction before API calls without modifying the core flow
    • Cross-realm symbol identity via Symbol.for() is the right choice
  2. Clean separation of concerns:

    • packages/core/src/v3/serverOnly/idempotencyKeys.ts: Server-side helpers with Zod validation via safeParse
    • packages/core/src/v3/idempotencyKeys.ts: SDK-side creation and extraction

Strengths 💪

  1. Consistent helper usage across the codebase:

    • runAttemptSystem.ts:269-270 - Uses getUserProvidedIdempotencyKey() and getIdempotencyKeyScope()
    • runsReplicationService.server.ts:895-896 - Uses extractIdempotencyKeyUser() and getIdempotencyKeyScope()
    • SpanPresenter.server.ts:233 and ApiRetrieveRunPresenter.server.ts:466 - Uses getUserProvidedIdempotencyKey()
  2. Proper Zod validation in server-only helpers (packages/core/src/v3/serverOnly/idempotencyKeys.ts:14, 30, 47) - uses safeParse to gracefully handle malformed data

  3. ClickHouse migration (internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql) follows the Goose format correctly with proper -- +goose Up and -- +goose Down markers

  4. Prisma migration is minimal and correct - just adds a nullable JSONB column

  5. Test coverage - The ClickHouse test (internal-packages/clickhouse/src/taskRuns.test.ts:67-68) includes the new fields in the test data

  6. Schema consistency - Both TaskRun and V3TaskRun schemas in packages/core/src/v3/schemas/common.ts:217-220, 380-383 properly add the new optional fields with good JSDoc comments


Minor Observations 📝

  1. Type assertion in SpanPresenter (apps/webapp/app/presenters/v3/SpanPresenter.server.ts - the getUserProvidedIdempotencyKey helper there): While it works, the inline type assertion run.idempotencyKeyOptions as { key?: string; scope?: string } | null could benefit from the same safeParse validation used in the server-only helpers for consistency. However, this is low risk since the data source is Prisma/DB which was validated on write.

  2. The resetIdempotencyKey function (packages/core/src/v3/idempotencyKeys.ts:226-280) is a nice addition that handles all the edge cases well - extracting options from IdempotencyKey objects, handling raw strings, and properly reconstructing hashes with scope suffixes.


Summary

This is a well-implemented feature that:

  • Solves a real debugging pain point
  • Uses elegant patterns (Symbol-based metadata)
  • Maintains backward compatibility
  • Has proper validation at server boundaries
  • Includes appropriate test coverage

The code is clean, well-organized, and follows the repository's patterns. LGTM 👍


| Branch: feat/expose-user-idempotency-key-in-context

@ericallam ericallam marked this pull request as ready for review January 17, 2026 12:29
@matt-aitken
Copy link
Member

@ericallam on the run page can we update the "Details" panel? If the user idempotency key is set show that, and the scope

@ericallam ericallam force-pushed the feat/expose-user-idempotency-key-in-context branch from c21d103 to 9b431a0 Compare January 19, 2026 17:04
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: 2

Caution

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

⚠️ Outside diff range comments (1)
packages/trigger-sdk/src/v3/shared.ts (1)

2065-2095: Apply idempotencyKeyOptions fallback to per-item batch runs when item-level keys are absent.

When only a batch-level idempotencyKey is provided, per-item runs currently skip idempotencyKeyOptions, losing scope information. Although finalIdempotencyKey correctly falls back to the batch-derived key, idempotencyKeyOptions becomes undefined, causing the server to lack the scope needed by getIdempotencyKeyScope().

Apply the fallback pattern consistently across all three batch locations (lines 2065–2095, 2200–2229, 2474–2504):

Fallback pattern
    const itemIdempotencyKey = await makeIdempotencyKey(item.options?.idempotencyKey);
    const finalIdempotencyKey = itemIdempotencyKey ?? batchItemIdempotencyKey;
+   const batchLevelIdempotencyKey = batchItemIdempotencyKey;
    const idempotencyKeyOptions = itemIdempotencyKey
      ? getIdempotencyKeyOptions(itemIdempotencyKey)
+     : batchLevelIdempotencyKey
+     ? getIdempotencyKeyOptions(batchLevelIdempotencyKey)
      : undefined;
🤖 Fix all issues with AI agents
In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx:
- Around line 85-90: The error handling currently interpolates
JSON.stringify(error) which can throw for circular/non-serializable payloads;
update the handler that calls logger.error and jsonWithErrorMessage so you
safely convert the error before building the message: replace
JSON.stringify(error) with a safe stringify routine (e.g., try {
JSON.stringify(error) } catch { fallback to util.inspect(error) or String(error)
}) or use a helper like safeStringify, and pass that sanitized string into
jsonWithErrorMessage while keeping the logger.error call (logger.error("Failed
to reset idempotency key", { error })) unchanged.

In `@packages/core/src/v3/idempotencyKeys.ts`:
- Around line 7-46: The current implementation attaches options to String
objects via IDEMPOTENCY_KEY_OPTIONS_SYMBOL and returns String instances from
idempotencyKeys.create(), which breaks primitive string typing and symbol
persistence; change the approach to always return primitive strings and replace
attachIdempotencyKeyOptions/getIdempotencyKeyOptions logic with a module-scoped
Map<string, IdempotencyKeyOptions> keyed by the string hash (store options in
the Map when creating keys and lookup by the primitive string in
getIdempotencyKeyOptions and resetIdempotencyKey), remove reliance on new
String(...) and IDEMPOTENCY_KEY_OPTIONS_SYMBOL, and update resetIdempotencyKey
and any callers to use primitive string checks (typeof === "string") and Map
lookups for options.
🧹 Nitpick comments (1)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx (1)

9-12: Handle invalid route params explicitly.
parse will throw on bad params and skip the action’s error handling. Consider safeParse to return a friendly message instead.

Suggested change
-  const { projectParam, organizationSlug, envParam, runParam } = v3RunParamsSchema.parse(params);
+  const parsedParams = v3RunParamsSchema.safeParse(params);
+  if (!parsedParams.success) {
+    return jsonWithErrorMessage({}, request, "Invalid run parameters");
+  }
+  const { projectParam, organizationSlug, envParam, runParam } = parsedParams.data;
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c21d103 and 9b431a0.

⛔ Files ignored due to path filters (1)
  • references/hello-world/src/trigger/idempotency.ts is excluded by !references/**
📒 Files selected for processing (21)
  • .changeset/bright-keys-shine.md
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • apps/webapp/app/services/runsReplicationService.server.ts
  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
  • internal-packages/clickhouse/src/taskRuns.test.ts
  • internal-packages/clickhouse/src/taskRuns.ts
  • internal-packages/database/prisma/migrations/20260116154810_add_idempotency_key_options_to_task_run/migration.sql
  • internal-packages/database/prisma/schema.prisma
  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • internal-packages/run-engine/src/engine/types.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • packages/core/src/v3/schemas/api.ts
  • packages/core/src/v3/schemas/common.ts
  • packages/core/src/v3/serverOnly/idempotencyKeys.ts
  • packages/core/src/v3/serverOnly/index.ts
  • packages/trigger-sdk/src/v3/shared.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • .changeset/bright-keys-shine.md
  • internal-packages/clickhouse/src/taskRuns.ts
  • internal-packages/database/prisma/migrations/20260116154810_add_idempotency_key_options_to_task_run/migration.sql
  • packages/core/src/v3/serverOnly/idempotencyKeys.ts
  • packages/core/src/v3/schemas/api.ts
  • packages/core/src/v3/schemas/common.ts
  • internal-packages/database/prisma/schema.prisma
  • apps/webapp/app/runEngine/services/triggerTask.server.ts
  • apps/webapp/app/services/runsReplicationService.server.ts
  • packages/core/src/v3/serverOnly/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:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
  • internal-packages/clickhouse/src/taskRuns.test.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/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
  • internal-packages/clickhouse/src/taskRuns.test.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/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • internal-packages/clickhouse/src/taskRuns.test.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
  • internal-packages/clickhouse/src/taskRuns.test.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/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
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/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.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/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
{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/core/src/v3/idempotencyKeys.ts
  • packages/trigger-sdk/src/v3/shared.ts
packages/trigger-sdk/**/*.{ts,tsx}

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

In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Files:

  • packages/trigger-sdk/src/v3/shared.ts
internal-packages/clickhouse/schema/**/*.sql

📄 CodeRabbit inference engine (CLAUDE.md)

internal-packages/clickhouse/schema/**/*.sql: ClickHouse migrations must use Goose format with -- +goose Up and -- +goose Down markers
Follow ClickHouse naming conventions: raw_ prefix for input tables, _v1, _v2 suffixes for versioning, _mv_v1 suffix for materialized views

Files:

  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
**/*.{test,spec}.{ts,tsx}

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

Use vitest for all tests in the Trigger.dev repository

Files:

  • internal-packages/clickhouse/src/taskRuns.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/clickhouse/src/taskRuns.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/clickhouse/src/taskRuns.test.ts
🧠 Learnings (28)
📓 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/**/*.{ts,tsx,js,jsx} : Scope idempotency keys globally or to current run using the scope parameter
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 `idempotencyKeyTTL` option to define a time window during which duplicate triggers return the original run
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
📚 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 `idempotencyKeyTTL` option to define a time window during which duplicate triggers return the original run

Applied to files:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.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} : Use `idempotencyKeys.create()` to create idempotency keys for preventing duplicate task executions

Applied to files:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
  • internal-packages/clickhouse/src/taskRuns.test.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} : Scope idempotency keys globally or to current run using the scope parameter

Applied to files:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/core/src/v3/idempotencyKeys.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
  • internal-packages/clickhouse/src/taskRuns.test.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:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • internal-packages/clickhouse/src/taskRuns.test.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:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • internal-packages/clickhouse/src/taskRuns.test.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:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
📚 Learning: 2026-01-15T11:50:06.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.044Z
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:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
  • internal-packages/clickhouse/src/taskRuns.test.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} : Subscribe to run updates using `runs.subscribeToRun()` for realtime monitoring of task execution

Applied to files:

  • internal-packages/run-engine/src/engine/index.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.batchTrigger()` to trigger multiple runs of a single task with different payloads

Applied to files:

  • internal-packages/run-engine/src/engine/index.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:

  • internal-packages/run-engine/src/engine/index.ts
  • internal-packages/run-engine/src/engine/types.ts
  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.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:

  • internal-packages/run-engine/src/engine/types.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:

  • internal-packages/run-engine/src/engine/types.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/app/v3/presenters/**/*.server.{ts,tsx} : Organize presenters in the webapp following the pattern `app/v3/presenters/*/*.server.ts` to move complex loader code into classes

Applied to files:

  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.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 logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks

Applied to files:

  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
📚 Learning: 2025-10-08T11:48:12.327Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2593
File: packages/core/src/v3/workers/warmStartClient.ts:168-170
Timestamp: 2025-10-08T11:48:12.327Z
Learning: The trigger.dev runners execute only in Node 21 and 22 environments, so modern Node.js APIs like AbortSignal.any (introduced in v20.3.0) are supported.

Applied to files:

  • apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.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 `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts
  • packages/trigger-sdk/src/v3/shared.ts
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
📚 Learning: 2026-01-15T11:50:06.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.044Z
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:

  • apps/webapp/app/presenters/v3/SpanPresenter.server.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 packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • packages/trigger-sdk/src/v3/shared.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/trigger-sdk/src/v3/shared.ts
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx
📚 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: Use the Run Engine 2.0 from `internal/run-engine` for new run lifecycle code in the webapp instead of the legacy run engine

Applied to files:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.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:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.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:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
📚 Learning: 2025-08-14T12:13:20.455Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2392
File: packages/cli-v3/src/utilities/gitMeta.ts:195-218
Timestamp: 2025-08-14T12:13:20.455Z
Learning: In the GitMeta schema (packages/core/src/v3/schemas/common.ts), all fields are intentionally optional to handle partial data from various deployment contexts (local, GitHub Actions, GitHub App). Functions like getGitHubAppMeta() are designed to work with missing environment variables rather than validate their presence.

Applied to files:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
📚 Learning: 2026-01-15T11:50:06.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.044Z
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:

  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
📚 Learning: 2026-01-15T11:50:06.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.044Z
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:

  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
📚 Learning: 2026-01-15T11:50:06.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.044Z
Learning: Applies to internal-packages/clickhouse/schema/**/*.sql : ClickHouse migrations must use Goose format with `-- +goose Up` and `-- +goose Down` markers

Applied to files:

  • internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql
🧬 Code graph analysis (6)
apps/webapp/app/presenters/v3/SpanPresenter.server.ts (1)
packages/core/src/v3/serverOnly/idempotencyKeys.ts (2)
  • getUserProvidedIdempotencyKey (10-19)
  • getIdempotencyKeyScope (27-35)
packages/core/src/v3/idempotencyKeys.ts (3)
packages/trigger-sdk/src/v3/idempotencyKeys.ts (1)
  • IdempotencyKey (8-8)
packages/core/src/v3/apiClient/index.ts (1)
  • resetIdempotencyKey (665-680)
packages/core/src/v3/apiClient/core.ts (1)
  • ZodFetchOptions (31-39)
packages/trigger-sdk/src/v3/shared.ts (1)
packages/core/src/v3/idempotencyKeys.ts (2)
  • makeIdempotencyKey (90-104)
  • getIdempotencyKeyOptions (29-33)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx (4)
apps/webapp/app/services/session.server.ts (1)
  • requireUserId (25-35)
apps/webapp/app/utils/pathBuilder.ts (1)
  • v3RunParamsSchema (35-37)
apps/webapp/app/models/message.server.ts (2)
  • jsonWithErrorMessage (125-142)
  • jsonWithSuccessMessage (106-123)
apps/webapp/app/v3/services/resetIdempotencyKey.server.ts (1)
  • ResetIdempotencyKeyService (5-36)
internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts (1)
packages/core/src/v3/serverOnly/idempotencyKeys.ts (2)
  • getUserProvidedIdempotencyKey (10-19)
  • getIdempotencyKeyScope (27-35)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx (3)
apps/webapp/app/components/primitives/CopyableText.tsx (1)
  • CopyableText (8-98)
apps/webapp/app/components/primitives/Tooltip.tsx (1)
  • InfoIconTooltip (110-139)
apps/webapp/app/utils/pathBuilder.ts (2)
  • docsPath (549-551)
  • v3RunIdempotencyKeyResetPath (344-353)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • 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 / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (20)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx (5)

1-9: LGTM!

Icon imports are correctly added and both BookOpenIcon and ClockIcon are used in the new idempotency UI section.


164-165: LGTM!

Good UX improvement. Showing existing data during revalidation instead of a loading spinner prevents UI flickering and provides a smoother experience.


423-428: LGTM!

Run ID display follows the existing pattern for copyable IDs and provides a convenient way for users to access the run identifier.


567-624: Well-structured idempotency info section.

The tooltip provides comprehensive documentation for users with clear explanations of scope and status values. The conditional status display handles all states appropriately, and the docs link follows the established pattern using docsPath().

One minor observation: the label content is quite complex with the nested flex containers, tooltip, and conditional actions. Consider extracting the tooltip content into a separate component if this pattern needs reuse elsewhere.


625-658: Properly implements the requested UI for idempotency details.

The display correctly surfaces the user-provided key, scope, and expiration as requested in the PR comments. The conditional "Expired/Expires" label based on status is a nice UX touch. All four idempotency properties (idempotencyKey, idempotencyKeyScope, idempotencyKeyExpiresAt, idempotencyKeyStatus) are correctly included in the SpanRun type and properly accessed.

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx (1)

3-3: Nice improvement: standardized response helpers.
Using the shared message helpers here keeps toast/session handling consistent across outcomes.

Also applies to: 41-46, 62-73

apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts (1)

12-12: LGTM for the idempotency key exposure update.

Also applies to: 41-43, 444-448

internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts (1)

33-36: LGTM for propagating user idempotency key and scope through context/execution payloads.

Also applies to: 200-202, 269-271, 430-432, 580-582

internal-packages/run-engine/src/engine/types.ts (1)

121-129: LGTM.

internal-packages/run-engine/src/engine/index.ts (1)

392-399: LGTM for persisting idempotencyKeyOptions on run creation.

Also applies to: 546-549

apps/webapp/app/presenters/v3/SpanPresenter.server.ts (1)

11-14: LGTM for surfacing user idempotency key, scope, and status in span/run payloads.

Also applies to: 236-240, 285-307, 386-389, 678-679

packages/core/src/v3/idempotencyKeys.ts (3)

48-60: LGTM.


141-153: LGTM.

Also applies to: 155-156


192-279: LGTM.

packages/trigger-sdk/src/v3/shared.ts (3)

14-14: Nice: user idempotency options are preserved for trigger + triggerAndWait.

Extracting options before stringifying the key keeps the original key/scope available while still sending the normalized key value.

Also applies to: 2118-2136, 2374-2404


1541-1560: Good: batch creation now carries idempotencyKeyOptions across both paths.

This keeps batch-level metadata consistent for array and streaming execution.

Also applies to: 1699-1703


2239-2255: LGTM: batch-level idempotencyKeyOptions are forwarded to createBatch.

Covers both array and streaming paths for batch trigger + batch trigger-and-wait.

Also applies to: 2300-2317, 2512-2530, 2581-2599

internal-packages/clickhouse/schema/013_add_task_runs_v2_idempotency_key_options.sql (1)

1-16: LGTM: clean Goose migration for user key + scope columns.

Up/Down sections are consistent and reversible.

internal-packages/clickhouse/src/taskRuns.test.ts (2)

66-68: LGTM: fixture includes the new idempotency key user/scope fields.


193-195: Good: additional test payloads updated for the new columns.

Also applies to: 243-245, 341-342

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 85 to 90
logger.error("Failed to reset idempotency key", { error });
return jsonWithErrorMessage(
submission,
{},
request,
`Failed to reset idempotency key: ${JSON.stringify(error)}`
);
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

Avoid potential crash when stringifying non-Error payloads.
JSON.stringify can throw on circular objects; consider a safe fallback.

Suggested change
-      return jsonWithErrorMessage(
-        {},
-        request,
-        `Failed to reset idempotency key: ${JSON.stringify(error)}`
-      );
+      let errorDetails: string;
+      try {
+        errorDetails = JSON.stringify(error);
+      } catch {
+        errorDetails = String(error);
+      }
+      return jsonWithErrorMessage(
+        {},
+        request,
+        `Failed to reset idempotency key: ${errorDetails}`
+      );
📝 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
logger.error("Failed to reset idempotency key", { error });
return jsonWithErrorMessage(
submission,
{},
request,
`Failed to reset idempotency key: ${JSON.stringify(error)}`
);
logger.error("Failed to reset idempotency key", { error });
let errorDetails: string;
try {
errorDetails = JSON.stringify(error);
} catch {
errorDetails = String(error);
}
return jsonWithErrorMessage(
{},
request,
`Failed to reset idempotency key: ${errorDetails}`
);
🤖 Prompt for AI Agents
In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.idempotencyKey.reset.tsx
around lines 85 - 90, The error handling currently interpolates
JSON.stringify(error) which can throw for circular/non-serializable payloads;
update the handler that calls logger.error and jsonWithErrorMessage so you
safely convert the error before building the message: replace
JSON.stringify(error) with a safe stringify routine (e.g., try {
JSON.stringify(error) } catch { fallback to util.inspect(error) or String(error)
}) or use a helper like safeStringify, and pass that sanitized string into
jsonWithErrorMessage while keeping the logger.error call (logger.error("Failed
to reset idempotency key", { error })) unchanged.

Comment on lines +7 to +46
export type IdempotencyKeyScope = "run" | "attempt" | "global";

export type IdempotencyKeyOptions = {
key: string;
scope: IdempotencyKeyScope;
};

const IDEMPOTENCY_KEY_OPTIONS_SYMBOL = Symbol.for("__idempotencyKeyOptions");

/**
* Extracts the user-provided key and scope from an idempotency key created with `idempotencyKeys.create()`.
*
* @param idempotencyKey The idempotency key to extract options from
* @returns The original key and scope, or undefined if the key doesn't have attached options
*
* @example
* ```typescript
* const key = await idempotencyKeys.create("my-key", { scope: "global" });
* const options = getIdempotencyKeyOptions(key);
* // options = { key: "my-key", scope: "global" }
* ```
*/
export function getIdempotencyKeyOptions(
idempotencyKey: IdempotencyKey | string
): IdempotencyKeyOptions | undefined {
return (idempotencyKey as any)[IDEMPOTENCY_KEY_OPTIONS_SYMBOL];
}

/**
* Attaches idempotency key options to a String object for later extraction.
* @internal
*/
function attachIdempotencyKeyOptions(
idempotencyKey: string,
options: IdempotencyKeyOptions
): IdempotencyKey {
const result = new String(idempotencyKey) as IdempotencyKey;
(result as any)[IDEMPOTENCY_KEY_OPTIONS_SYMBOL] = options;
return result;
}
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:

cat packages/core/src/v3/idempotencyKeys.ts

Repository: triggerdotdev/trigger.dev

Length of output: 9353


🏁 Script executed:

# Search for usages of createIdempotencyKey and idempotencyKeys.create() to see how the result is used
rg -n "createIdempotencyKey|idempotencyKeys\.create" --type=ts --type=tsx -A 5 -B 2

Repository: triggerdotdev/trigger.dev

Length of output: 96


🏁 Script executed:

# Search for usages of createIdempotencyKey and idempotencyKeys.create()
rg -n "createIdempotencyKey|idempotencyKeys\.create" -A 5 -B 2

Repository: triggerdotdev/trigger.dev

Length of output: 32785


🏁 Script executed:

# Search for comparisons or typeof checks on idempotencyKey values
rg -n "typeof.*idempotencyKey|idempotencyKey.*==|idempotencyKey.*instanceof" --type=ts -B 2 -A 2

Repository: triggerdotdev/trigger.dev

Length of output: 8086


🏁 Script executed:

# Check for strict equality comparisons with idempotencyKey
rg "idempotencyKey\s*===" -B 2 -A 2

Repository: triggerdotdev/trigger.dev

Length of output: 2265


🏁 Script executed:

# Get the context around line 235 in resetIdempotencyKey
sed -n '230,250p' packages/core/src/v3/idempotencyKeys.ts

Repository: triggerdotdev/trigger.dev

Length of output: 926


🏁 Script executed:

# Check the IdempotencyKey type definition to understand the design intent
rg -A 5 "type IdempotencyKey" packages/core/src/v3/types/

Repository: triggerdotdev/trigger.dev

Length of output: 178


🏁 Script executed:

# Check the Branded type definition
rg -B 5 -A 5 "type Branded" packages/core/src/v3/types/

Repository: triggerdotdev/trigger.dev

Length of output: 1179


Return primitive strings from idempotencyKeys.create() instead of String objects.

The current implementation wraps the hash in new String(), which breaks the Branded<string> type contract and causes runtime issues. At line 235 in resetIdempotencyKey(), the check typeof idempotencyKey === "string" will fail for String objects (returning "object"), forcing all created keys through a slower path. While defensive instanceof checks elsewhere mask this, relying on them throughout the codebase is fragile and error-prone. Additionally, Symbol-attached properties don't survive serialization, risking silent data loss.

Store options in a module-scoped Map<string, IdempotencyKeyOptions> keyed by the hash instead, and return primitive strings consistently. This eliminates type confusion, improves performance, and ensures the API contract is honestly fulfilled.

🛠️ Suggested refactor
-const IDEMPOTENCY_KEY_OPTIONS_SYMBOL = Symbol.for("__idempotencyKeyOptions");
+const IDEMPOTENCY_KEY_OPTIONS_BY_HASH = new Map<string, IdempotencyKeyOptions>();

 export function getIdempotencyKeyOptions(
   idempotencyKey: IdempotencyKey | string
 ): IdempotencyKeyOptions | undefined {
-  return (idempotencyKey as any)[IDEMPOTENCY_KEY_OPTIONS_SYMBOL];
+  const key =
+    typeof idempotencyKey === "string"
+      ? idempotencyKey
+      : idempotencyKey instanceof String
+      ? idempotencyKey.valueOf()
+      : undefined;
+  if (!key) return undefined;
+  return IDEMPOTENCY_KEY_OPTIONS_BY_HASH.get(key);
 }

 function attachIdempotencyKeyOptions(
   idempotencyKey: string,
   options: IdempotencyKeyOptions
 ): IdempotencyKey {
-  const result = new String(idempotencyKey) as IdempotencyKey;
-  (result as any)[IDEMPOTENCY_KEY_OPTIONS_SYMBOL] = options;
-  return result;
+  IDEMPOTENCY_KEY_OPTIONS_BY_HASH.set(idempotencyKey, options);
+  return idempotencyKey as IdempotencyKey;
 }
🤖 Prompt for AI Agents
In `@packages/core/src/v3/idempotencyKeys.ts` around lines 7 - 46, The current
implementation attaches options to String objects via
IDEMPOTENCY_KEY_OPTIONS_SYMBOL and returns String instances from
idempotencyKeys.create(), which breaks primitive string typing and symbol
persistence; change the approach to always return primitive strings and replace
attachIdempotencyKeyOptions/getIdempotencyKeyOptions logic with a module-scoped
Map<string, IdempotencyKeyOptions> keyed by the string hash (store options in
the Map when creating keys and lookup by the primitive string in
getIdempotencyKeyOptions and resetIdempotencyKey), remove reliance on new
String(...) and IDEMPOTENCY_KEY_OPTIONS_SYMBOL, and update resetIdempotencyKey
and any callers to use primitive string checks (typeof === "string") and Map
lookups for options.

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.

3 participants