Skip to content

fix(node): Skip registration-only instrumentations in the runtime loader - #24240

Merged
mydea merged 5 commits into
developfrom
fn/fix-orchestrion-register
Sep 11, 2026
Merged

fix(node): Skip registration-only instrumentations in the runtime loader#24240
mydea merged 5 commits into
developfrom
fn/fix-orchestrion-register

Conversation

@mydea

@mydea mydea commented Sep 9, 2026

Copy link
Copy Markdown
Member

Registration-only orchestrion configs — native-channel libraries such as ai v7, ioredis, @redis/client, mysql2 and mongoose — carry the custom MODULE_REGISTRATION_TRANSFORM. That transform is wired into the bundler plugins only (orchestrion/bundler/moduleInjectedTransform.ts, via bundler/options.ts's customTransforms); the runtime loader (@sentry/server-runtime-injection's register) passes no custom transforms to initialize(). So when one of these modules loads through the runtime hook, getTransformer returns a transformer whose .transform is undefined and the loader throws TypeError: transform is not a function. The diagnostics callback treats any TypeError as a stripped/bundled transformer and emits the always-on warning:

[Sentry] `@sentry/server-runtime-injection` was bundled into your application, so <module> and any other instrumented dependency load uninstrumented. ...

…even though nothing is wrong: these libraries publish their own tracing channels and are instrumented via setupOnce() / waitForTracingChannelBinding, independently of the module-registration snippet.

This excludes registration-only configs from a new SENTRY_RUNTIME_INSTRUMENTATIONS set used by the runtime loader; the bundler keeps the full SENTRY_INSTRUMENTATIONS. Skipping them at runtime is lossless rather than a workaround: the registration snippet only fires orchestrion.module-injected, which drives the orchestrion:* subscription these native versions never publish (a no-op at runtime). The snippet earns its keep only on the bundler path (e.g. bundler-only SDKs like @sentry/cloudflare that discover a loaded module via that event); @sentry/node registers its integrations statically.

Root cause: the sentryModuleRegistration custom transform used by registration-only configs is registered in orchestrion/bundler/* but never in the runtime path, so the runtime code transformer cannot apply it.

Noticed this here: #24228

Tests:

  • server-utils unit test — SENTRY_RUNTIME_INSTRUMENTATIONS drops every registration-only config and keeps the rest, and asserts the exclusion is per-config, not per-module.
  • node-integration-test — loading a native-channel library (mysql2 ≥ 3.20) with the runtime loader active emits no transformer-unavailable warning (via ensureNoErrorOutput); it fails against the unfiltered set and passes with this change.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 28.92 kB - -
@sentry/browser - with treeshaking flags 27.21 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 27.11 kB - -
@sentry/browser (incl. Tracing) 50.42 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 50.43 kB - -
@sentry/browser (incl. Tracing, Profiling) 53.41 kB - -
@sentry/browser (incl. Tracing, Replay) 89.93 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 79.05 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 94.64 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 107.65 kB - -
@sentry/browser (incl. Feedback) 46.41 kB - -
@sentry/browser (incl. sendFeedback) 33.97 kB - -
@sentry/browser (incl. FeedbackAsync) 39.08 kB - -
@sentry/browser (incl. Metrics) 29.93 kB - -
@sentry/browser (incl. Logs) 30.2 kB - -
@sentry/browser (incl. Metrics & Logs) 30.87 kB - -
@sentry/react 30.67 kB - -
@sentry/react (incl. Tracing) 52.73 kB - -
@sentry/vue 36.16 kB - -
@sentry/vue (incl. Tracing) 52.66 kB - -
@sentry/svelte 28.93 kB - -
CDN Bundle 30.66 kB - -
CDN Bundle (incl. Tracing) 50.93 kB - -
CDN Bundle (incl. Logs, Metrics) 32.93 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 52.91 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 73.62 kB - -
CDN Bundle (incl. Tracing, Replay) 88.48 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 90.43 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 94.55 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 96.55 kB - -
CDN Bundle - uncompressed 90.73 kB - -
CDN Bundle (incl. Tracing) - uncompressed 152.17 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 97.31 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 158.13 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 226.72 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 271.74 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 277.69 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 285.44 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 291.38 kB - -
@sentry/nextjs (client) 55.05 kB - -
@sentry/sveltekit (client) 50.83 kB - -
@sentry/core/server 37.08 kB - -
@sentry/core/browser 13.66 kB - -
@sentry/node 128.6 kB +0.03% +26 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 81.83 kB +0.02% +15 B 🔺
@sentry/node - without tracing 89.57 kB +0.06% +47 B 🔺
@sentry/node - without channel injection 107.52 kB +0.03% +22 B 🔺
@sentry/aws-serverless 97.78 kB +0.04% +34 B 🔺
@sentry/cloudflare (withSentry) - minified 203.02 kB - -
@sentry/cloudflare (withSentry) 505.43 kB - -

View base workflow run

@mydea
mydea marked this pull request as ready for review September 9, 2026 11:41
@mydea
mydea requested review from a team as code owners September 9, 2026 11:41
@mydea
mydea requested review from JPeer264 and isaacs and removed request for a team September 9, 2026 11:41
mydea added a commit that referenced this pull request Sep 10, 2026
…he runtime warning (#24242)

The runtime loader emitted the always-on `[Sentry]
@sentry/server-runtime-injection was bundled ... loads uninstrumented`
warning for **any** `TypeError` thrown while transforming a module. That
equated "a transform threw a TypeError" with "the transformer was
stripped by a bundler", so an unrelated per-module failure was reported
as a bundling problem — pointing users at externalizing the package,
which can itself break other setups.

This makes the warning honest and self-diagnosing:

- **Include the underlying error** in the message, so the reader can see
the actual cause instead of a hardcoded diagnosis they can't verify
(`debug: true` still logs the full error/stack).
- **Only claim "bundled" for the transform pipeline itself going
missing** — `parse`/`generate is not a function`, the fingerprint of a
bundler tree-shaking the vendored meriyah/astring parser, which fails
every module the same way — guarded by nothing having been instrumented
yet (a transformer that already instrumented something is provably not
stripped). Any other transform `TypeError` (e.g. `transform is not a
function`) now gets a scoped `Could not instrument <module> (...)`
message that points at reporting it, not changing the build.

_Root cause of the misclassification_: the callback branched on `error
instanceof TypeError` alone. The systemic (stripped-transformer) case
has a distinct signature (`parse`/`generate is not a function`), so
classifying on that — with the success marker as a secondary guard —
separates it from isolated per-module failures with a reasonable success
rate.

A message-based heuristic is the pragmatic fix here; a follow-up
upstream change to the transformer (`nodejs/orchestrion-js`) to throw
typed/coded errors would let this classification be exact rather than
string-matched.

Related to #24240, which removes the specific registration-only cause
that surfaced this; this hardens the warning for any remaining/future
cause.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@isaacs isaacs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One suggestion to consider, but this LGTM. Good find, correctly fixed. 👍

* integrations statically, so it does not need it.
*/
export const SENTRY_RUNTIME_INSTRUMENTATIONS: InstrumentationConfig[] = SENTRY_INSTRUMENTATIONS.filter(
config => config.transform !== MODULE_REGISTRATION_TRANSFORM,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As this is written, it filters out anything that isn't using the specific MODULE_REGISTRATION_TRANSFORM, but I think in practice, it should be just config.transform === undefined, because any named transform will have the same effect, right? Otherwise, if we add some other named transform someday, we're back to the same false alarm warning.

Suggested change
config => config.transform !== MODULE_REGISTRATION_TRANSFORM,
config => config.transform === undefined,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

right, good call, adjusted!


// ...while every other config is preserved, unchanged and in order.
expect(SENTRY_RUNTIME_INSTRUMENTATIONS).toEqual(
SENTRY_INSTRUMENTATIONS.filter(c => c.transform !== MODULE_REGISTRATION_TRANSFORM),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test is just a copy of the same implementation, so it's not really doing a lot. Can we test that the properties we actually care about are set the way we need them to be?

mydea and others added 5 commits September 11, 2026 09:31
Registration-only orchestrion configs (native-channel libraries — ai v7,
ioredis, @redis/client, mysql2, mongoose) carry a custom transform wired into
the bundler plugins only. The runtime loader (`@sentry/server-runtime-injection`
`register`) has no custom transforms, so transforming these modules threw
`TypeError: transform is not a function`, which the diagnostics callback
misreported as the always-on "`@sentry/server-runtime-injection` was bundled
... loads uninstrumented" warning — even though the libraries are correctly
instrumented via their native channel (`setupOnce` / `waitForTracingChannelBinding`).

Exclude registration-only configs from the runtime instrumentation set
(`SENTRY_RUNTIME_INSTRUMENTATIONS`). This is lossless: at runtime the snippet
would only trigger a no-op subscription to `orchestrion:*` channels these
versions never publish.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- server-utils unit test: `SENTRY_RUNTIME_INSTRUMENTATIONS` drops every
  registration-only config and keeps the rest in order, and documents the
  affected native-channel modules (`@redis/client`, `ai`, `ioredis`, `mongoose`,
  `mysql2`), asserting the exclusion is per-config not per-module.
- node-integration-test: with the runtime loader active, loading a native-channel
  library (`mysql2` >= 3.20) emits no "transform is not a function" /
  "server-runtime-injection was bundled" warning (`ensureNoErrorOutput`). This
  fails against the unfiltered set and passes with the fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`.toContain([...])` checks for the whole array as a single member, so it never
matched. Use `arrayContaining` so the assertion documents the known
native-channel modules without breaking when another such library is added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mydea
mydea force-pushed the fn/fix-orchestrion-register branch from 4822bef to 4e66dcb Compare September 11, 2026 07:42
@mydea
mydea merged commit 2a58493 into develop Sep 11, 2026
449 of 451 checks passed
@mydea
mydea deleted the fn/fix-orchestrion-register branch September 11, 2026 08:14
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