Skip to content

fix(node): restore tsx runtime instrumentation - #25351

Open
jpdefo wants to merge 4 commits into
getsentry:developfrom
jpdefo:feat/tsx-runtime-injection
Open

jpdefo wants to merge 4 commits into
getsentry:developfrom
jpdefo:feat/tsx-runtime-injection

Conversation

@jpdefo

@jpdefo jpdefo commented Oct 14, 2026 •

Copy link
Copy Markdown

Restore runtime instrumentation for CommonJS dependencies loaded through tsx on Node 24.13+. Keep synchronous module hooks and use the CommonJS compiler for custom require loaders. Match canonical file URLs and source fingerprints to avoid transforming the same source twice while preserving the fallback for source changed by another loader.

Regression coverage checks native Node and tsx with CommonJS reloads, query and fragment imports, and cache eviction after queried imports. The tsx-express E2E application now inherits Node 24 and all 10 existing tests pass locally. The two existing nft tracing tests fail on Windows with both this patch and the original code.

Fixes #24787

Fixes getsentry#24787.

Signed-off-by: João Pedro de Faria <jpdefo7@hotmail.com>
Co-Authored-By: GPT-6.1 Sol <codex@openai.com>
@github-actions github-actions Bot added the external PR from an external contributor label Oct 14, 2026
@jpdefo
jpdefo marked this pull request as ready for review October 14, 2026 07:55
@jpdefo
jpdefo requested a review from a team as a code owner October 14, 2026 07:55
@jpdefo
jpdefo requested review from isaacs and mydea and removed request for a team October 14, 2026 07:55
@JPeer264
JPeer264 self-requested a review October 14, 2026 07:57
@JPeer264

Copy link
Copy Markdown
Member

Thanks a lot for opening the PR. Before I go deeper on the review could you remove this line to run this specific e2e test again on Node v24?

@cursor cursor 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.

Stale Bugbot comment from a previous run.

Comment thread packages/server-runtime-injection/src/register.ts Outdated
Run the tsx-express E2E application on the inherited Node 24 runtime.

Co-Authored-By: GPT-6.1 Sol <codex@openai.com>
@jpdefo

jpdefo commented Oct 14, 2026

Copy link
Copy Markdown
Author

Removed the tsx-express application's Node 20 override so it inherits Node 24.21.0. All 10 existing tsx-express E2E tests pass on that version with the updated local SDK packages, without changing assertions or timeouts.

Also addressed the URL-key issue reported by Bugbot. The duplicate-transform guard now normalizes query/fragment URLs and compares source fingerprints before skipping the compiler transform, preserving the fallback when another loader changes the source. Added query, fragment, and cache-eviction coverage. All eight focused regressions, the full development build, and lint pass. The runtime-injection unit suite has 18 passes and the same two previously reproduced Windows nft tracing failures.

@JPeer264

Copy link
Copy Markdown
Member

bugbot run

@cursor cursor 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.

Stale Bugbot comment from a previous run.

mydea added a commit that referenced this pull request Oct 14, 2026
Under Node >= 24.13 the SDK injects instrumentation via Module.registerHooks,
which tsx's CJS loader bypasses. Keep the pin until #25351 lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mydea added a commit that referenced this pull request Oct 15, 2026
Most e2e apps that pinned their own Node version did so to run on
something newer than the old Node 20 base. Since #24343 moved the root
to Node 24.21.0, those pins are either redundant or now *older* than the
root (e.g. the `24.15.0` pins), so every app now simply extends the root
Volta config.

This also drops the Node 20 pin that #24343 added to
`react-send-to-sentry` as a stopgap, since it passes on Node 24 now.

Two overrides remain:

- pnpm `11.10.0` in `nuxt-5` and `nuxt-5-sourcemaps`, which was bumped
deliberately in #22017.
- Node `20.19.5` in `tsx-express`. On Node >= 24.13 the SDK injects
instrumentation through `Module.registerHooks`, whose `load` hook tsx's
CJS loader bypasses (it replaces `Module._extensions` and calls
`_compile` directly), so Express is never instrumented. #25351 fixes
this; the pin can go once it lands.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

@JPeer264 JPeer264 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.

I think it works overall. I added some comments and once they're addressed we can merge this.

if (typeof mod.registerHooks === 'function' && stableSyncHooks) {
initialize({ instrumentations: SENTRY_RUNTIME_INSTRUMENTATIONS });
mod.registerHooks({ resolve, load: globalAny.Deno ? withDenoFormats(load) : load });
const isNode = !globalAny.Bun && !globalAny.Deno;

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.

Is there a specific reason why you exclude Bun and Deno for this? Based on my research it should actually work in Deno and Bun wouldn't actually hit this line as the typeof mod.registerHooks === 'function' would prevent that.

So I guess removing the isNode check would work OOTB already.

FWIW our node-integration-tests automatically run on Bun and Deno, so we would check against these too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You’re right, the additional isNode guard was unnecessary. I’ve removed it and updated the unit test to verify that Deno installs the CommonJS fallback. The targeted Deno integration tests pass, and I confirmed that Bun 1.4.0 does not expose registerHooks, so the existing capability check handles it.


const DataLoader = require('dataloader');
const value = await new DataLoader(async keys => keys).load('real-key');
await Sentry.close(1000);

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.

Is it specifically required to close the client? I think we could remove the line to check it works without it

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I removed Sentry.close(1000) and ran the reload test with both plain Node and tsx. Both exited normally and recorded exactly one operation and one injection. The same scenario also passed directly on Deno.

jpdefo and others added 2 commits October 16, 2026 06:38
Allow the CommonJS fallback on runtimes with stable synchronous hooks and exercise reloads without closing the client.

Co-Authored-By: GPT-6.1 Sol <codex@openai.com>
@jpdefo

jpdefo commented Oct 16, 2026

Copy link
Copy Markdown
Author

@JPeer264 It's probably ready now, please double check the changes. Thank you!

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1efa955. Configure here.

// `Module.registerHooks` / `Module.register` are newer than the @types/node
// we build against, hence the cast.
const mod = Module as NodeModule;
const mod = Module as unknown as NodeModule;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Double-cast lacks typing justification

Low Severity

The new as unknown as NodeModule double-cast has no comment explaining why a safer type is impossible. The nearby note only covers registerHooks / register, not the added Module.prototype._compile shape that forced the double-cast. Flagged because the PR review rules require that justification on new SDK double-casts.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 1efa955. Configure here.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external PR from an external contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Runtime injection does not instrument modules loaded by tsx on Node 24.13+

2 participants