Skip to content

@sentry/cloudflare/nodejs_compat segfaults workerd: barrel statically re-exports prismaIntegration from @sentry/node #22519

Description

@msnelling

Package

@sentry/cloudflare (10.66.0)

Summary

Importing anything from @sentry/cloudflare/nodejs_compat inside a real workerd isolate kills the runtime with a bare SIGSEGV — no JS error, no stack trace, nothing catchable.

The cause is line 1 of that entry point:

// build/esm/nodejs_compat/index.js
export { prismaIntegration } from '@sentry/node';

(the CJS build does the same via require('@sentry/node'))

@sentry/node cannot be loaded in workerd. Because it is a static re-export from the barrel, any consumer of that entry drags the whole Node SDK into the isolate, regardless of which binding they actually asked for.

This does not affect deployed Workers, because esbuild tree-shakes the unused re-export out of the bundle. It breaks contexts that load modules unbundled, most notably @cloudflare/vitest-pool-workers — so the code ships fine and then cannot be tested.

Reproduction

Minimal standalone project — four dev-dependencies, five files, inlined in full below. Happy to push it to a repo if that's easier to work with.

package.json

{
  "name": "repro", "private": true, "type": "module",
  "devDependencies": {
    "@cloudflare/vitest-pool-workers": "0.18.6",
    "@sentry/cloudflare": "10.66.0",
    "vitest": "4.1.10",
    "wrangler": "4.112.0"
  }
}

wrangler.jsonc

{
  "name": "repro",
  "main": "worker.ts",
  "compatibility_date": "2026-02-19",
  "compatibility_flags": ["nodejs_compat"]
}

worker.ts

export default { fetch: () => new Response('ok') };

vitest.config.ts

import { cloudflareTest } from '@cloudflare/vitest-pool-workers';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  plugins: [cloudflareTest({ wrangler: { configPath: './wrangler.jsonc' } })],
  test: { include: ['*.test.ts'] },
});

repro.test.ts

import { it, expect } from 'vitest';
import { vercelAIIntegration } from '@sentry/cloudflare/nodejs_compat';

it('imports the nodejs_compat entry', () => {
  expect(typeof vercelAIIntegration).toBe('function');
});

npx vitest run

*** Received signal #11: Segmentation fault: 11
stack: .../node_modules/@cloudflare/workerd-linux-64/bin/workerd@25674af ...

Error: [vitest-pool]: Worker cloudflare-pool emitted error.
Caused by: Error: Worker exited unexpectedly

 Test Files   (1)
      Tests  no tests

Expected vs actual

Expected: the entry point imports, or fails with a catchable JS error naming the unsupported module.
Actual: the workerd process dies with SIGSEGV. In a multi-file test run this silently drops the affected file — the reporter prints Test Files 4 passed (5) and a non-zero exit, with no indication of which file or why.

Narrowing

Each of these was run in isolation:

Import Result
import { vercelAIIntegration } from '@sentry/cloudflare' (root entry) ✅ passes
import { vercelAIIntegration } from '@sentry/cloudflare/nodejs_compat' 💥 SIGSEGV
import { prismaIntegration } from '@sentry/node' 💥 SIGSEGV
deep path .../build/esm/nodejs_compat/integrations/tracing/vercelai.js ✅ passes

So the AI integration itself — including its @sentry/server-utils ai:telemetry subscriber — is perfectly workerd-safe. Only the @sentry/node re-export in the barrel is fatal.

Why this is hard to work around

Using the root entry instead is not equivalent. build/esm/integrations/tracing/vercelai.js imports only from @sentry/core, whereas the nodejs_compat variant composes it with vercelAiIntegration from @sentry/server-utils:

const inner = vercelAiIntegration(options);          // the node:diagnostics_channel 'ai:telemetry' subscriber
const instrumentation = vercelAIIntegration$1(options); // the ai.* -> gen_ai.* span processor
return { ...inner, ...instrumentation };

Only that composed version subscribes to ai:telemetry. With AI SDK v7 (which no longer emits OTel spans itself) the root entry produces no AI spans at all — so anyone wanting AI monitoring on Cloudflare must use nodejs_compat, which is exactly the entry that segfaults.

The deep path works but is not a supported subpath: exports only declares ., ./request and ./nodejs_compat, so there is no public way to reach the integration without the barrel.

Suggested fixes

Any one of these would resolve it:

  1. Don't statically re-export @sentry/node from a workerd entry point. Load prismaIntegration lazily inside the integration factory, so merely importing the barrel doesn't pull in the Node SDK.
  2. Drop prismaIntegration from the Cloudflare entry — it's a Node-only integration, and Prisma-on-Workers is its own discussion (Support Prisma Integration in Cloudflare / D1 #15466).
  3. Expose the AI integration on its own export subpath (e.g. @sentry/cloudflare/nodejs_compat/vercelai) so consumers can bypass the barrel.

(1) or (2) seem cleanest, since they also shrink what every nodejs_compat consumer bundles.

This looks like the same class of bug as #20038 (workerd/worker export conditions resolving to @sentry/node, breaking Cloudflare Workers).

Workaround

For anyone else hitting this — alias @sentry/node to a stub in the workers-pool vitest config. This makes the test module graph match what esbuild produces for production, where @sentry/node is absent anyway:

resolve: {
  alias: { '@sentry/node': path.resolve(__dirname, 'sentry-node-stub.ts') },
},

...where the stub exports a throwing prismaIntegration, so a genuine use fails loudly rather than silently.

Environment

  • @sentry/cloudflare 10.66.0, @sentry/server-utils 10.66.0
  • @cloudflare/vitest-pool-workers 0.18.6, vitest 4.1.10, wrangler 4.112.0, miniflare 4.20260714.0
  • workerd 1.20260714.1 — reproduced on both workerd-darwin-arm64 (local) and workerd-linux-64 (GitHub Actions ubuntu-latest)
  • compatibility_date 2026-02-19, compatibility_flags: ["nodejs_compat"] (also reproduces on 2025-10-14)

Metadata

Metadata

Assignees

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions