Skip to content

fix(node): stop esbuild failing when @opentelemetry/api is not installed - #5422

Merged
posthog[bot] merged 2 commits into
mainfrom
posthog-self-driving/fixnode-stop-esbuild-failing-on-52ac87
Oct 25, 2026
Merged

posthog[bot] merged 2 commits into
mainfrom
posthog-self-driving/fixnode-stop-esbuild-failing-on-52ac87

Conversation

@posthog

@posthog posthog Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Problem

  • Apps that bundle posthog-node 5.56.1 or later with esbuild get a failed build when they do not install the optional peer @opentelemetry/api. This blocks deploys after a routine version bump (AWS CDK NodejsFunction, SST, Serverless esbuild, tsup with noExternal).
  • trace-context.ts used a literal import('@opentelemetry/api'). The runtime try/catch cannot help, because esbuild resolves a literal specifier at bundle time.

Origin

  • Issues: #5420
  • First signal: 2026-10-24
  • Inbox report: open
  • Task started by: auto-start, after the report was rated P1 and ready to fix

Changes

  • getActiveTraceProperties now reads the active span from the global registry of @opentelemetry/api v1 (Symbol.for('opentelemetry.js.api.1')) and the span context key (Symbol.for('OpenTelemetry Context Key SPAN')). The package does not import OpenTelemetry anymore, so no bundler tries to resolve it.
  • New test esbuild-bundle.spec.ts bundles the built ESM and CJS entrypoints with esbuild while @opentelemetry/* cannot resolve. It fails on main and passes with this change. test:unit already depends on build in turbo, so the test reads dist.
  • esbuild is now a dev dependency of posthog-node (same ^0.25.0 range as other packages, version already in the lockfile).
Approach esbuild without OTel webpack / Next.js with OTel OTel bundled into the app
Literal import() (before) ❌ build fails ✅ ✅
import(variable) ✅ ⚠️ "Critical dependency" warning, import replaced with an empty context, no trace IDs ❌ fails at runtime if node_modules is not deployed
Global registry (this PR) ✅ ✅ ✅

Warning

The global key is how all copies of @opentelemetry/api v1 share one context manager, but it is not public API. A future @opentelemetry/api v2 would use a new key. Our peer range is ^1.2.0, so v2 is out of range today. If the key changes, the only effect is that exceptions get no $trace_id / $span_id. Capture still works.

Release info Sub-libraries affected

Libraries affected

  • All of them
  • posthog-js (web)
  • posthog-js-lite (web lite)
  • posthog-node
  • posthog-react-native
  • @posthog/react-native-plugin
  • @posthog/react
  • @posthog/ai
  • @posthog/convex
  • @posthog/next
  • @posthog/nextjs-config
  • @posthog/nuxt
  • @posthog/openfeature-node-provider
  • @posthog/openfeature-web-provider
  • @posthog/rollup-plugin
  • @posthog/webpack-plugin
  • @posthog/types
  • @posthog/browser-common

Checklist

  • All commits are signed (see Commit signing)
  • Tests for new code
  • Accounted for the impact of any changes across different platforms
  • Accounted for backwards compatibility of any changes (no breaking changes!)
  • Took care not to unnecessarily increase the bundle size

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Fully autonomous

  • Agent: Claude Code in PostHog Desktop, from an inbox report.
  • First I tried a variable specifier with /* webpackIgnore: true */. rslib (rspack) removes webpack and Turbopack magic comments from dist, also with importDynamic: false (rslib already sets it). A local webpack 5 build of import(variable) gave the "Critical dependency" warning, so I rejected that approach (see the table).
  • The existing exception-trace-context.spec.ts tests use the real @opentelemetry/api with AsyncLocalStorageContextManager, so they now verify the global-registry read path.
  • Checks run locally: posthog-node lint, check-types, test:unit (all pass), test:edge-compat, oxfmt --check, test:dependency-cooldown. I did not do a manual test in a real CDK or SST project.

Created with PostHog Desktop from this inbox report, addressing #5420.

Closes #5420

🤖 Generated with Claude Code

Read the active span from the global registry of @opentelemetry/api v1. Do not import the optional peer. A literal import made esbuild fail the build when the peer was not installed. A variable import makes webpack warn and drop the module.

Add a test that bundles the built ESM and CJS entrypoints with esbuild when @opentelemetry/api cannot resolve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 0d603b98-9825-4f1c-8261-e50c33c8a122
@posthog posthog Bot added the self-driving label Oct 24, 2026
@posthog
posthog Bot requested a review from pauldambra October 24, 2026 20:04

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not approved — this change needs a human reviewer.

Re-adding the stamphog label gives the same result unless the changed files change.

stamphog can't approve this pull request automatically, and the refusal is final. The deny-list gate flagged it for the deps_toolchain category, most likely because it adds esbuild to packages/node/package.json and changes pnpm-lock.yaml. The tier gate also classified it as T2-never (55 lines across 5 files, touching two areas), so it needs a human reviewer. The size gate passed, so splitting is not required. The author can ask a maintainer to review it. Moving the dependency and lockfile change into its own pull request might also make the remaining change easier to review.

Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✗ matches: deps_toolchain
size ✓ 19L, 3F substantive, 55L/5F incl. docs/generated/snapshots — within ceiling
tier ✗ classified as T2-never: T2-never (55L, 5F, two-areas, fix)
stamphog 2.6.1 .stamphog/policy.yml @ unknown · reviewed head 8e04f6a

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PostHog Review · Standard · reviewed 8e04f6a: no findings.

This Standard review started from the posthog-review label. Comment @posthog review --deep for a Deep review, or @posthog help to see all commands.

@posthog posthog Bot removed the posthog-review label Oct 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Coherence

4 spec problems · 0 failing chokepoints · 2 spec gaps · 0 guard failures

44 components, 7 bullets (5 invariants, 2 requirements), 4 problems

Spec problems (spec --check)

  • packages/ai/src/otel/Otel.spec.md:7 an invariant bullet starts with <name>: <sentence>
  • packages/ai/src/otel/Otel.spec.md:8 an invariant bullet starts with <name>: <sentence>
  • Posthog-js.practice.md:20 practice add an invariant cites 187a5c5, which is no commit in this repository
  • Posthog-js.practice.md:20 practice add an invariant cites 8922ab2, which is no commit in this repository

Chokepoints: none declared.

Spec gaps: 2 entrances with outside or unknown trust and no traced control, and 10 more held by the adoption baseline

  • packages/web/Web.spec.md entrance public api (trust host-app): packages/web
  • packages/webpack-plugin/Webpack-plugin.spec.md entrance webpack plugin (trust build): packages/webpack-plugin

Close one with guard: <chokepoint>, an invariant that names it on its entrances: line, or control: none — <reason>; scaffold control proposes it.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Node SDK compliance v2

Commit: 4a0232c
Workflow run and artifacts

Capture v0 — acceptance

✅ 28 passed / ❌ 3 non-passing / ⚠️ 853 not selected

Drill-down: run artifacts → node-v2-v0 → acceptance/report.json.diagnostics.json (match case_id below).

❌ server_snapshot_payload_false — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:
❌ server_snapshot_payload_zero — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:
❌ server_snapshot_payload_null — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:

Harness exit: 1; report validation exit: 1.

Capture v1 — acceptance

✅ 28 passed / ❌ 3 non-passing / ⚠️ 853 not selected

Drill-down: run artifacts → node-v2-v1 → acceptance/report.json.diagnostics.json (match case_id below).

❌ server_snapshot_payload_false — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:
❌ server_snapshot_payload_zero — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:
❌ server_snapshot_payload_null — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:

Harness exit: 1; report validation exit: 1.

Capture v0 — migration

✅ 55 passed / ❌ 1 non-passing / ⚠️ 101 not selected

Drill-down: run artifacts → node-v2-v0 → reports/report.json.diagnostics.json (match case_id below).

❌ migration/yaml-parity-v1/remote-flags-v1.feature::Omitted GeoIP settings default to enabled in flag requests — failed_assertion

Flags field differs

Code: flag_request_field

Failed step: migration/yaml-parity-v1/remote-flags-v1.feature:134

the first flags request field "geoip_disable" should equal JSON false

Operation: /flags/

Field: geoip_disable

Expected:

false

Actual value:

true

Harness exit: 1; report validation exit: 1.

Capture v1 — migration

✅ 120 passed / ❌ 1 non-passing / ⚠️ 36 not selected

Drill-down: run artifacts → node-v2-v1 → reports/report.json.diagnostics.json (match case_id below).

❌ migration/yaml-parity-v1/remote-flags-v1.feature::Omitted GeoIP settings default to enabled in flag requests — failed_assertion

Flags field differs

Code: flag_request_field

Failed step: migration/yaml-parity-v1/remote-flags-v1.feature:134

the first flags request field "geoip_disable" should equal JSON false

Operation: /flags/

Field: geoip_disable

Expected:

false

Actual value:

true

Harness exit: 1; report validation exit: 1.

@pauldambra
pauldambra marked this pull request as ready for review October 25, 2026 10:36
@pauldambra
pauldambra requested a review from a team as a code owner October 25, 2026 10:36
…iling-on-52ac87

Generated-By: PostHog Desktop
Task-Id: a33bb94c-53b3-481d-bb7a-146daa4a51fe

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not approved — this change needs a human reviewer.

Re-adding the stamphog label gives the same result unless the changed files change.

stamphog refused this pull request at two gates. The deny-list gate flagged it for deps_toolchain, which here is the new esbuild dev dependency in packages/node/package.json and the matching pnpm-lock.yaml change. The tier gate classified it as T2-never, because it touches two areas (the trace-context.ts fix plus the dependency and toolchain changes), and that tier is never auto-approved. The size gate passed, so splitting the PR for size isn't needed.

The next step is to ask a human reviewer to look at it. The author could also split it so the trace-context.ts change and its test are separate from the dependency change, though a human would still need to review both parts.

  • pauldambra reviewed the current head.
Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✗ matches: deps_toolchain
size ✓ 19L, 3F substantive, 55L/5F incl. docs/generated/snapshots — within ceiling
tier ✗ classified as T2-never: T2-never (55L, 5F, two-areas, fix)
stamphog 2.6.1 .stamphog/policy.yml @ unknown · reviewed head 4a0232c

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PostHog Review · Standard · reviewed 4a0232c: no findings.

This Standard review started from the posthog-review label. Comment @posthog review --deep for a Deep review, or @posthog help to see all commands.

@posthog posthog Bot removed the posthog-review label Oct 25, 2026
@posthog
posthog Bot added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit b5036c4 Oct 25, 2026
73 checks passed
@posthog
posthog Bot deleted the posthog-self-driving/fixnode-stop-esbuild-failing-on-52ac87 branch October 25, 2026 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

posthog-node ≥ 5.56.1 breaks esbuild bundles: Could not resolve "@opentelemetry/api" when the optional peer isn't installed

1 participant