Repository navigation
Nitro: move the runtime error-hook plugin out of @sentry/nitro for full cross-runtime instrumentation #24900
Description
Follow-up to the nitro → @sentry/server-utils move (#24861). That move made the span, cache, and Server-Timing instrumentation shared (nitroIntegration + nitroServerTimingIntegration in @sentry/server-utils), and they auto-register on bundler-only SDKs like @sentry/cloudflare via orchestrion registration-only configs. But a plain Nitro app running on another runtime (Cloudflare/Deno/Bun/Node) via just its runtime SDK still does not get the full instrumentation, because two pieces remain tied to @sentry/nitro:
- The runtime error-hook plugin — registers
captureErrorHookon the Nitro app'serrorhook. - Enabling Nitro's
tracingChannel— a build-time config flag that turns on the native h3/srvx/unstorage channels the integrations subscribe to.
(Source-map upload also lives in @sentry/nitro, but that's a build concern handled by the bundler/framework plugin and is out of scope here.)
Goal: a Nitro app deployed on any runtime gets the full Nitro instrumentation — including error capture — from its runtime SDK alone, without depending on @sentry/nitro.
Why the error hook can't be a runtime integration today
@sentry/nitro/src/runtime/plugins/server.ts is a Nitro app plugin:
export default definePlugin(nitroApp => {
nitroApp.hooks.hook('error', captureErrorHook);
});- It uses
nitroApp.hooks.hook('error', …)— a Nitro-app API. A Sentry integration'ssetup(client)only has the client, not the Nitro app, so it cannot register this hook. - The plugin is injected at build time by
@sentry/nitro's module (instrumentServer→addPlugin). Without that build module, nothing injects it. captureErrorHookis a broad error boundary that the tracing channels can't replace (this is why it wasn't moved with the rest). It catches errors from Nitro'scaptureErrorcall sites: h3onError,onRequest/onResponsehooks (tags: ['request'|'response']), plugin init (['plugin']), cache (['cache']), and process-levelunhandledRejection/uncaughtException(error/hooks.mjs). It also filtersHTTPError3xx/4xx, attachescontexts.nitro(method/path/tags), and awaitsflushIfServerless(). The tracing channels only see errors thrown through a traced handler, so they'd lose most of this.
Approaches to make it cross-runtime
1. (Preferred, needs Nitro upstream) Nitro emits errors on a diagnostics channel
If Nitro published its error boundary through a node:diagnostics_channel (e.g. nitro.error) alongside hooks.callHook('error', …), a runtime integration (nitroIntegration, or a dedicated nitroErrorIntegration) could subscribe to it — fully runtime-agnostic, no app plugin and no build-time injection needed, and it would auto-register on Cloudflare via the same orchestrion mechanism already used for spans. Blocked on a Nitro feature request / upstream change.
2. Register the hook at runtime via the Nitro app global
Nitro stores the app on globalThis.__nitro__[APP_ID] and exposes useNitroApp(). A runtime integration could obtain the app and call nitroApp.hooks.hook('error', captureErrorHook) itself. Challenges to work out:
- Timing: the app is created lazily (on first
useNitroApp()), typically afterSentry.init(). The integration would need to defer registration (poll, patchuseNitroApp, or hook app creation) so it doesn't miss the app or register twice. - Coupling: depends on Nitro's internal global shape (
__nitro__,APP_ID) and thehookableAPI, which are less stable than a public channel. HTTPErrortyping:captureErrorHookimportsHTTPErrorfromnitro/h3; a runtime-registerable version in@sentry/server-utilswould need to vendor a minimal type or duck-typestatus(server-utils is framework-neutral).
3. Enabling tracingChannel without @sentry/nitro
The native channels only fire if the Nitro build sets tracingChannel: true (resolveTracingOptions in Nitro bails otherwise). Today only @sentry/nitro's withSentryConfig sets it. For a runtime-SDK-only setup, either:
- the app author sets
tracingChannel: trueinnitro.config.ts, or - the framework module (Nuxt/SolidStart/…) sets it (note: see the Nuxt-adoption caveats — enabling it there conflicts with Nuxt's own instrumentation), or
- document it as a required user step.
This is inherently build-time, so a runtime SDK can't do it alone.
What each runtime SDK would need
Once approach 1 or 2 makes error capture runtime-registerable in @sentry/server-utils:
- Re-export the error integration from each runtime SDK (as they already do for
nitroIntegration). - Extend the orchestrion registration-only config so the error integration registers on bundler-only SDKs too (same mechanism as the span integration).
Open questions / blockers
- Does Nitro have — or would it accept — a diagnostics-channel error surface? (Approach 1 hinges on this.)
- Is reading the Nitro app from
globalThis.__nitro__reliable across runtimes and Nitro versions? (Approach 2.) - Is there any non-build path to enable
tracingChannel? (Likely no.) - Should source-map upload stay in
@sentry/nitro/ the bundler plugin, or is a per-runtime story wanted there too?
References
- Move PR: ref(nitro): Move channel-based instrumentation into @sentry/server-utils #24861
- Error hook:
packages/nitro/src/runtime/plugins/server.ts,packages/nitro/src/runtime/hooks/captureErrorHook.ts - tracingChannel enablement:
packages/nitro/src/config.ts - Shared integrations:
packages/server-utils/src/integrations/nitro/* - Orchestrion registration:
packages/server-utils/src/orchestrion/config/nitro.ts,channel-integration-definitions.ts
Activity
linear-code commentedon Sep 30, 2026
JS-3858