ref(server-utils): Move ServerRuntimeClient, node stack parser and server-only utils out of core - #23833
ref(server-utils): Move ServerRuntimeClient, node stack parser and server-only utils out of core#23833mydea wants to merge 8 commits into
Conversation
172fba6 to
d52ac53
Compare
b8c658c to
0f1963a
Compare
6a3662a to
d85e36e
Compare
size-limit report 📦
|
d85e36e to
194f1bd
Compare
194f1bd to
6396adc
Compare
…rver-only utils out of core Continues slimming `@sentry/core` down to its isomorphic surface by moving the remaining server-only APIs into `@sentry/server-utils`. Moved out of core: - `flushIfServerless` + `vercelWaitUntil` (used by the meta-framework SDKs) - `trpcMiddleware` - `callFrameToStackFrame` / `watchdogTimer` (the anr worker helpers) - `loadModule` (split out of `utils/node`; `isNodeEnv` stays, since core depends on it via `isBrowser`) - the base `ServerRuntimeClient` (with `ServerRuntimeClientOptions` / `ServerRuntimeOptions`) and the node stack-trace parser (`nodeStackLineParser`, `node`, `filenameIsInApp`) `@sentry/server-utils` is added as a dependency to `@sentry/node-native` and `@sentry/bundler-plugins`, the only two consumers that didn't already have it. Every other SDK already depended on server-utils and keeps re-exporting these under the same names, so there is no user-facing change. `mcp-server` and the `integrations/http/*` subtree stay in core. `ServerRuntimeClient` and the stack parser reach for a few core building blocks that aren't public. Rather than route them through the semi-internal `@sentry/core/server` entry, the three that are genuinely needed are exported from the public `@sentry/core` entry (`getTraceInfoFromScope`, `addUserAgentToTransportHeaders`, `normalizeStackTracePath`); the other two are dropped — the transport buffer size becomes a local constant, and the span-streaming integration name is read off the integration instance. No metric or transport internals are exposed. `ServerRuntimeOptions` was extracted from the shared `types/options.ts` (which stays in core) into its own server-utils file. Keeping the moved code out of edge/client bundles: relocating this into `@sentry/server-utils` surfaced Next.js bundling regressions (every `next build` e2e app failed with `UnhandledSchemeError` on `node:async_hooks` / `node:net`), because server-only server-utils code was reaching the edge and browser bundles, which can't resolve `node:` builtins. This was latent before the move (the same paths pulled these helpers from browser-safe `@sentry/core`). - Make the `@sentry/server-utils` barrel tree-shakeable: re-export the deprecated `attachHapiErrorHandler` normally with the deprecation moved onto the source function (matching `attachKoaErrorHandler`) instead of a non-shakeable `const` re-export that pinned the whole barrel graph. - Point `@sentry/vercel-edge` at `@sentry/server-utils/no-diagnostic-channels` for `ServerRuntimeClient` / `nodeStackLineParser` / `trpcMiddleware`, so the heavy barrel (Node integrations, `node:net`) never reaches the Next.js edge bundle. - Split the server-only App-Router wrappers and `captureRequestError` out of the client-reachable `common` barrel into `common/serverOnlyExports`, re-exported only from the server and edge entrypoints. Make `responseEnd` client-safe by inlining `vercelWaitUntil`, so the dual-bundled pages-router `_error` path no longer pulls `node:async_hooks` into the browser bundle. `vercelWaitUntil` stays in server-utils for `flushIfServerless`. Relocates the node-stack-parsing tests to `@sentry/server-utils`; keeps the core-internal unit tests (`metadata`, `debug-ids`, `third-party-errors-filter`) in core, fed by a local node stack parser fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASNdTRtxNEjNMEBGCxENT7
After `ServerRuntimeClient` and `nodeStackLineParser` moved from the edge-safe `@sentry/core/server` entry into `@sentry/server-utils`, `client.ts` and `vendor/stacktrace.ts` pulled the full `@sentry/server-utils` barrel (which subscribes to `node:diagnostics_channel`) into the `wrapRequestHandler` graph, breaking runtimes without `nodejs_compat` (e.g. Shopify Oxygen / the remix-hydrogen app). Route those two imports through `@sentry/server-utils/no-diagnostic-channels` — the edge-safe subset that carries `ServerRuntimeClient` and the node stack parser without the channel subscription — matching how `@sentry/vercel-edge` already consumes them. Update the `requestModuleGraph` guard to allow that one edge-safe entry while still forbidding the heavy barrel and `@sentry/node`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU
…move - node `httpServerIntegration` test imported `ServerRuntimeClient` from `@sentry/core/server`, which no longer exports it — import it from `@sentry/server-utils`. - tanstackstart-react `wrapFetchWithSentry` test mocked `@sentry/server-utils` without spreading the original module, so `nodeStackLineParser` (now pulled from server-utils by the `@sentry/node` init path) was undefined and the mock factory threw. Spread `importOriginal()` like the sibling mocks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU
`nodeStackLineParser` moved out of `@sentry/core/server` into `@sentry/server-utils`. Update the deno unit tests (`mod`, `deno-runtime-metrics`) and the deno integration `direct-client-acs` scenario to import it from `@sentry/server-utils`; the deno SDK src already does. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU
`@sentry/bundler-plugins` now externalizes `@sentry/server-utils` (it `require`s it at build time for `ServerRuntimeClient` / `nodeStackLineParser`). The fixtures pinned `@sentry/core` and `@sentry/bundler-plugins` to local tarballs but not server-utils, so pnpm pulled a published `@sentry/server-utils` from the registry that mismatched the local `@sentry/core` build (`SPAN_KIND` export error), failing every bundler run. Pack `@sentry/server-utils` in `setup.mjs` and add a `pnpm.overrides` entry for it to every fixture, pointing at the local tarball like core and bundler-plugins. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU
The nuxt-3 app imported `flushIfServerless` from `@sentry/core/server`, which no longer exports it after the move to `@sentry/server-utils`. Use the public `Sentry.flush()` (already imported as `SentryNode`) instead of reaching into an internal entry point. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU
The Hydrogen (Oxygen) e2e app `remix-hydrogen` failed to build: its client bundle
pulled `@sentry/server-utils/async-context` (`import { AsyncLocalStorage } from
'node:async_hooks'`), which Vite externalizes into an empty stub, throwing
`"AsyncLocalStorage" is not exported by "__vite-browser-external"`.
`async-context` is the only module in the shared `exports.ts` surface that
statically imports a `node:` builtin, yet it was re-exported from both the `index`
and `no-diagnostic-channels` barrels — so any browser/edge bundle importing *any*
helper from those barrels dragged `node:async_hooks` into the graph.
- Move `setAsyncLocalStorageAsyncContextStrategy` out of the shared `exports.ts`
into a dedicated `@sentry/server-utils/async-context` entry, so `index` /
`no-diagnostic-channels` are free of `node:` builtins. Update its consumers
(Node/Deno/Cloudflare SDKs + tests), all of which run where `node:async_hooks`
resolves.
- Point the two `@sentry/remix/cloudflare`-reachable imports (`instrumentServer`'s
`loadModule`, `cloudflare/index`'s `trpcMiddleware`) at the lean
`no-diagnostic-channels` barrel instead of the full `@sentry/server-utils`
barrel, whose Node-only integrations (`tedious` → `node:events`, …) otherwise
reach the bundled Hydrogen client build.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU
6396adc to
a6bdaf3
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a6bdaf3. Configure here.
| @@ -1,5 +1,5 @@ | |||
| import { captureException, getClient, getCurrentScope } from '@sentry/core'; | |||
| import { flushIfServerless } from '@sentry/core/server'; | |||
| import { flushIfServerless } from '@sentry/server-utils'; | |||
There was a problem hiding this comment.
Nuxt pulls the full server-utils barrel
Medium Severity
flushIfServerless and trpcMiddleware now come from the full @sentry/server-utils barrel instead of @sentry/core/server or @sentry/server-utils/no-diagnostic-channels. That barrel still statically re-exports Node integrations (node:net, diagnostic channels). This is flagged because the review rules forbid unguarded Node builtins on edge-capable SDK paths, the same failure this PR fixed for Next.js and vercel-edge.
Additional Locations (2)
Triggered by project rule: PR Review Guidelines for Cursor Bot
Reviewed by Cursor Bugbot for commit a6bdaf3. Configure here.


Continues slimming
@sentry/coredown to its isomorphic surface by moving the remaining server-only APIs into@sentry/server-utils.Moved out of core:
flushIfServerless+vercelWaitUntil(used by the meta-framework SDKs)trpcMiddlewarecallFrameToStackFrame/watchdogTimer(the anr worker helpers)loadModule(split out ofutils/node;isNodeEnvstays, since core depends on it viaisBrowser)ServerRuntimeClient(withServerRuntimeClientOptions/ServerRuntimeOptions) and the node stack-trace parser (nodeStackLineParser,node,filenameIsInApp)@sentry/server-utilsis added as a dependency to@sentry/node-nativeand@sentry/bundler-plugins, the only two consumers that didn't already have it. Every other SDK already depended on server-utils and keeps re-exporting these under the same names, so there is no user-facing change.mcp-serverand theintegrations/http/*subtree stay in core.ServerRuntimeClientand the stack parser reach for a few core building blocks that aren't public. Rather than route them through the semi-internal@sentry/core/serverentry, the three that are genuinely needed are exported from the public@sentry/coreentry —getTraceInfoFromScope,addUserAgentToTransportHeaders,normalizeStackTracePath— and the other two dependencies are dropped: the transport buffer size becomes a local constant, and the span-streaming integration name is read off the integration instance. No metric or transport internals are exposed.ServerRuntimeOptionswas extracted from the sharedtypes/options.ts(which stays in core) into its own server-utils file.Keeping the moved code out of edge/client bundles
Relocating this into
@sentry/server-utilssurfaced Next.js bundling regressions — everynext builde2e app failed withUnhandledSchemeErroronnode:async_hooks/node:net.Root cause: server-only
@sentry/server-utilscode (which statically importsnode:async_hooksvia the async-context strategy, andnode:netvia the firebase integration) was reaching the Next.js edge and browser bundles, which can't resolvenode:builtins. This was latent before the move — the same code paths pulled these helpers from browser-safe@sentry/core. Fixed at each layer:@sentry/server-utilsbarrel re-exportedattachHapiErrorHandlervia aconstbinding (export const x = _x) — a module-level statement that pinned the whole barrel graph against tree-shaking, so importing any one symbol dragged in every Node integration. It's now a plain re-export with the@deprecatedmarker moved onto the source function (matchingattachKoaErrorHandler).@sentry/vercel-edgenow importsServerRuntimeClient/nodeStackLineParser/trpcMiddlewarefrom the lean@sentry/server-utils/no-diagnostic-channelsentry (as it already did for its AI/OTLP integrations), so the heavy barrel never reaches the Next.js edge bundle.common, which pulled the server-only App-Router wrappers (wrapServerComponent,wrapRouteHandler,wrapMiddleware,wrapGenerationFunction,withServerActionInstrumentation) andcaptureRequestErrorinto the browser bundle. These moved tocommon/serverOnlyExports, re-exported only from the server and edge entrypoints. And because the pages-router_errorinstrumentation (captureUnderscoreErrorException) is legitimately dual-bundled and usesresponseEnd'swaitUntil,responseEndnow inlines a client-safevercelWaitUntilinstead of importing it from server-utils (vercelWaitUntilstays in server-utils forflushIfServerless).Tests
The node-stack-parsing tests move to
@sentry/server-utilsalongside the parser. Core unit tests that only used the parser as a realistic fixture (metadata,debug-ids,third-party-errors-filter) stay in core with a local node-stack-parser fixture, so core keeps that coverage without depending on server-utils.🤖 Generated with Claude Code
https://claude.ai/code/session_01ASNdTRtxNEjNMEBGCxENT7