Skip to content

fix(core): drop unique-symbol brand on LocalsKey to fix dual-package builds#3626

Merged
ericallam merged 1 commit into
mainfrom
fix/locals-key-dual-package
May 15, 2026
Merged

fix(core): drop unique-symbol brand on LocalsKey to fix dual-package builds#3626
ericallam merged 1 commit into
mainfrom
fix/locals-key-dual-package

Conversation

@ericallam
Copy link
Copy Markdown
Member

Summary

LocalsKey<T> (the type returned by locals.create()) was branded with a
module-level declare const __local: unique symbol. Each such declaration
is its own nominal type, and tshy emits separate .d.ts files for the
ESM and CJS outputs — each gets its own __local symbol. Under certain
pnpm hoisting layouts a single TypeScript compilation can resolve
LocalsKey from both the ESM source path and the CJS dist path within
the same call site, producing two structurally-incompatible variants of
the same type. TS surfaces this as the misleading error:

Argument of type 'LocalsKey<X>' is not assignable to parameter of type
'LocalsKey<X>'. Property '[__local]' is missing in type 'LocalsKey<X>'
but required in type 'BrandLocal<X>'.

The error has been hitting CI on PRs opened since the chat.agent stack
landed (e.g. #3625 typecheck job), but doesn't reproduce on developer
machines where the pnpm node_modules layout was built up incrementally.

Fix

Replace the unique symbol brand with an optional phantom field that
carries T at the type level:

// before
declare const __local: unique symbol;
type BrandLocal<T> = { [__local]: T };
export type LocalsKey<T> = BrandLocal<T> & {
  readonly id: string;
  readonly __type: unique symbol;
};

// after
export type LocalsKey<T> = {
  readonly id: string;
  readonly __type: symbol;
  /** Phantom carrier for the value type — never read at runtime. */
  readonly __valueType?: T;
};

The ESM and CJS .d.ts outputs now produce structurally identical types,
so cross-output resolution no longer produces a mismatch. T is still
carried at the type level via the optional phantom field. The runtime
shape is unchanged — manager.ts was already casting via as unknown,
which is no longer needed.

Test plan

  • pnpm run typecheck --filter @trigger.dev/core --filter @trigger.dev/sdk
  • pnpm run build --filter @trigger.dev/core --filter @trigger.dev/sdk
    (clean rebuild) — confirms the ESM and CJS dist .d.ts outputs
    no longer carry distinct unique symbol declarations
  • pnpm --filter @trigger.dev/core test test/mockTaskContext.test.ts --run
  • pnpm --filter @trigger.dev/sdk test test/mockChatAgent.test.ts --run

…builds

LocalsKey<T> was branded with a module-level `declare const __local: unique
symbol`. tshy emits separate .d.ts files for the ESM and CJS outputs, and
each gets its own `declare const __local: unique symbol` — TypeScript
treats every such declaration as a nominally distinct type.

Under certain pnpm hoisting layouts a single TypeScript compilation can
resolve LocalsKey from both the ESM source path and the CJS dist path
within the same call site. With unique-symbol brands the two variants are
structurally incompatible — TS rejects passing one to a function that
expects the other, with a misleading 'Property [__local] is missing' error.

Replace the symbol brand with an optional phantom value-type field. T is
still carried at the type level, the runtime shape is unchanged, and the
ESM and CJS .d.ts outputs are now identical.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 15, 2026

🦋 Changeset detected

Latest commit: df1cad3

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

This PR includes changesets to release 32 packages
Name Type
@trigger.dev/core Patch
@trigger.dev/build Patch
trigger.dev Patch
@trigger.dev/plugins Patch
@trigger.dev/python Patch
@trigger.dev/redis-worker Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/sdk Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@trigger.dev/rbac 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
references-ai-chat 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
@internal/sdk-compat-tests 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
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Review Change Stack

Walkthrough

This PR fixes a TypeScript type compatibility issue with LocalsKey<T> across dual (ESM/CJS) package builds. The core change replaces a unique symbol-based nominal branding approach with a structural phantom-type design using __type: symbol and an optional __valueType?: T field. The LocalsManager implementations in NoopLocalsManager and StandardLocalsManager are then updated to return object literals directly, eliminating the previous type casts that were needed under the old branding scheme.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: removing the unique-symbol brand from LocalsKey to resolve dual-package build incompatibilities.
Description check ✅ Passed The PR description provides comprehensive context, a clear problem statement, detailed fix explanation, and a concrete test plan, though it doesn't explicitly follow the repository template sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/locals-key-dual-package

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/core/src/v3/locals/types.ts (1)

1-20: ⚡ Quick win

Add @crumbs markers to this changed block.

The edits around LocalsKey<T> don’t include // @Crumbs (or a `// `#region` `@crumbs wrapper). Please annotate this block per workflow.

As per coding guidelines, “Add crumbs as you write code — not just when debugging. Mark lines with // @Crumbs or wrap blocks in `// `#region` `@crumbs.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/v3/locals/types.ts` around lines 1 - 20, The LocalsKey<T>
declaration block is missing the required crumbs annotations; add a crumbs
marker to this changed block by inserting a comment marker (either a line
comment // `@crumbs` on the declaration or wrap the block with // `#region` `@crumbs`
... // `#endregion` `@crumbs`) immediately surrounding the export type LocalsKey<T>
(including its id, __type and __valueType members) so the block is annotated per
the project's crumb workflow.
packages/core/src/v3/locals/manager.ts (1)

6-8: ⚡ Quick win

Please add @crumbs annotations for these edits.

Both updated return-object blocks are missing breadcrumb markers (// @Crumbs or `// `#region` `@crumbs).

As per coding guidelines, “Add crumbs as you write code — not just when debugging. Mark lines with // @Crumbs or wrap blocks in `// `#region` `@crumbs.”

Also applies to: 24-26

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/v3/locals/manager.ts` around lines 6 - 8, The updated
return-object blocks that set "__type: Symbol()" and "id" are missing breadcrumb
annotations; add inline breadcrumb comments (e.g., "// `@crumbs`") or wrap the
return-object blocks with "// `#region` `@crumbs`" markers around the object
literal(s) that include "__type" and "id" so they follow the project's
guideline; make the same change for the other block referenced around lines
24-26 to ensure both return-object sections in manager.ts are annotated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/v3/locals/manager.ts`:
- Around line 6-8: The updated return-object blocks that set "__type: Symbol()"
and "id" are missing breadcrumb annotations; add inline breadcrumb comments
(e.g., "// `@crumbs`") or wrap the return-object blocks with "// `#region` `@crumbs`"
markers around the object literal(s) that include "__type" and "id" so they
follow the project's guideline; make the same change for the other block
referenced around lines 24-26 to ensure both return-object sections in
manager.ts are annotated.

In `@packages/core/src/v3/locals/types.ts`:
- Around line 1-20: The LocalsKey<T> declaration block is missing the required
crumbs annotations; add a crumbs marker to this changed block by inserting a
comment marker (either a line comment // `@crumbs` on the declaration or wrap the
block with // `#region` `@crumbs` ... // `#endregion` `@crumbs`) immediately surrounding
the export type LocalsKey<T> (including its id, __type and __valueType members)
so the block is annotated per the project's crumb workflow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7a86ee36-24df-4499-a08c-2e13ba674355

📥 Commits

Reviewing files that changed from the base of the PR and between 0510fd6 and df1cad3.

📒 Files selected for processing (3)
  • .changeset/locals-key-dual-package-fix.md
  • packages/core/src/v3/locals/manager.ts
  • packages/core/src/v3/locals/types.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (28)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: typecheck / typecheck
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

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

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

**/*.{ts,tsx}: Import from @trigger.dev/core subpaths only, never from the root. Subpath imports must be used to maintain proper module boundaries.
When writing Trigger.dev tasks, always import from @trigger.dev/sdk. Never use @trigger.dev/sdk/v3 or deprecated client.defineJob.
Prisma is version 6.14.0. Use the Prisma client from internal-packages/database for all database operations.
For ClickHouse client, schema migrations, and analytics queries, use internal-packages/clickhouse.

Files:

  • packages/core/src/v3/locals/types.ts
  • packages/core/src/v3/locals/manager.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:

  • packages/core/src/v3/locals/types.ts
  • packages/core/src/v3/locals/manager.ts
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Add crumbs as you write code — not just when debugging. Mark lines with // @Crumbs or wrap blocks in `// `#region` `@crumbs. They stay on the branch throughout development and are stripped by agentcrumbs strip before merge.

Files:

  • packages/core/src/v3/locals/types.ts
  • packages/core/src/v3/locals/manager.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:

  • packages/core/src/v3/locals/types.ts
  • packages/core/src/v3/locals/manager.ts
packages/core/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/core/CLAUDE.md)

Never import the root package (@trigger.dev/core). Always use subpath imports such as @trigger.dev/core/v3, @trigger.dev/core/v3/utils, @trigger.dev/core/logger, or @trigger.dev/core/schemas

Files:

  • packages/core/src/v3/locals/types.ts
  • packages/core/src/v3/locals/manager.ts
**/*.{ts,tsx,js,jsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Code formatting is enforced using Prettier. Run pnpm run format before committing

Files:

  • packages/core/src/v3/locals/types.ts
  • packages/core/src/v3/locals/manager.ts
packages/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying any public package (packages/* or integrations/*), add a changeset using pnpm run changeset:add. Default to patch for bug fixes and minor changes; confirm with maintainers before selecting minor; never select major without explicit approval.

Files:

  • packages/core/src/v3/locals/types.ts
  • packages/core/src/v3/locals/manager.ts
🧠 Learnings (2)
📚 Learning: 2026-03-22T13:26:12.060Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:12.060Z
Learning: In the triggerdotdev/trigger.dev codebase, do not flag `navigator.clipboard.writeText(...)` calls for `missing-await`/`unhandled-promise` issues. These clipboard writes are intentionally invoked without `await` and without `catch` handlers across the project; keep that behavior consistent when reviewing TypeScript/TSX files (e.g., usages like in `apps/webapp/app/components/code/TextEditor.tsx`).

Applied to files:

  • packages/core/src/v3/locals/types.ts
  • packages/core/src/v3/locals/manager.ts
📚 Learning: 2026-03-22T19:24:14.403Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts:200-204
Timestamp: 2026-03-22T19:24:14.403Z
Learning: In the triggerdotdev/trigger.dev codebase, webhook URLs are not expected to contain embedded credentials/secrets (e.g., fields like `ProjectAlertWebhookProperties` should only hold credential-free webhook endpoints). During code review, if you see logging or inclusion of raw webhook URLs in error messages, do not automatically treat it as a credential-leak/secrets-in-logs issue by default—first verify the URL does not contain embedded credentials (for example, no username/password in the URL, no obvious secret/token query params or fragments). If the URL is credential-free per this project’s conventions, allow the logging.

Applied to files:

  • packages/core/src/v3/locals/types.ts
  • packages/core/src/v3/locals/manager.ts
🔇 Additional comments (1)
.changeset/locals-key-dual-package-fix.md (1)

1-6: LGTM!

@ericallam ericallam enabled auto-merge (squash) May 15, 2026 06:49
@ericallam ericallam merged commit ac02c0f into main May 15, 2026
46 checks passed
@ericallam ericallam deleted the fix/locals-key-dual-package branch May 15, 2026 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants