Repository navigation
Description
Environment
- SDK:
@sentry/hono@10.53.1,@sentry/bun@10.53.1 - Runtime: Bun
- Framework: Hono
Summary
The Bun Hono middleware does not follow the same initialization pattern as the Node Hono middleware.
For Node, @sentry/hono/node expects Sentry to be initialized separately, usually by loading an instrument.ts file with node --import. The middleware checks whether a client exists and warns if Sentry has not been initialized.
For Bun, @sentry/hono/bun's sentry(app, options) unconditionally calls init(options) internally. That conflicts with the Bun preload/instrumentation pattern, where the SDK is initialized from an instrument.ts file loaded via Bun preload before the application imports.
Documentation context
The preload setup was intentional. The Sentry Bun SDK docs describe creating an instrument.js/instrument.ts file and loading it with Bun preload, for example:
bun --preload ./instrument.ts server.tsSo this is not only an attempt to copy the Node --import pattern onto Bun. It is following the documented Bun SDK setup model where initialization happens in a separate instrumentation file before the application entrypoint is loaded.
The confusing part is that the Hono Bun middleware appears to expect middleware-owned initialization, while the Bun SDK docs point users toward preload-owned initialization.
What happens
When following the preload pattern in a Bun Hono service:
// instrument.ts
import * as Sentry from "@sentry/hono/bun";
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
});and then mounting the Bun Hono middleware:
import * as Sentry from "@sentry/hono/bun";
import { Hono } from "hono";
const app = new Hono();
app.use("*", Sentry.sentry(app, {
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
}));Sentry is initialized twice: once from the Bun preload file and again from the middleware.
In our service this produced duplicate OpenTelemetry registration/debug output and nested/duplicated http.server spans. For health check routes, we also saw catch-all transaction names such as GET /* or GET http://*/, which made it look like route filtering/naming was not working.
Catch-all route naming behavior
There also appears to be a route naming issue in the shared Hono request handler.
@sentry/hono/build/esm/shared/middlewareHandlers.js uses routePath(context, -1) to pick the route name:
const lastMatchedRoute = routePath(context, -1);
activeSpan.updateName(`${context.req.method} ${lastMatchedRoute}`);
updateSpanName(rootSpan, `${context.req.method} ${lastMatchedRoute}`);In a Hono app with mounted sub-apps and wildcard middleware, routePath(context, -1) can return the last matched wildcard middleware route rather than the route that actually handled the request.
For example, a request to GET /readyz can have a matched route list shaped like:
ALL /* root middleware, including Sentry
GET /readyz actual handler
ALL /* later mounted sub-app middlewareThe later mounted ALL /* route may not execute after /readyz handles the request, but it is still present in Hono's matched route list. Because Sentry uses routePath(context, -1), it picks the trailing wildcard and names the transaction GET /*.
After await next(), Hono's current route index points at the handler that produced the response. In our local workaround, using routePath(context) after next() returns the expected route (/readyz), while routePath(context, -1) can still return /*.
So the catch-all transaction names are not only a filtering issue. They can come from using the last matched route instead of the current resolved route after Hono has dispatched the handler.
Why this is surprising
The Node middleware and Bun middleware differ here:
// @sentry/hono/build/esm/node/middleware.js
const sentry = (app) => {
const sentryClient = getClient();
if (sentryClient === undefined) {
debug.warn("Sentry is not initialized...");
}
applyPatches(app);
return async (context, next) => { ... };
};// @sentry/hono/build/esm/bun/middleware.js
const sentry = (app, options) => {
const isDebug = options.debug;
isDebug && debug.log("Initialized Sentry Hono middleware (Bun)");
init(options);
applyPatches(app);
return async (context, next) => { ... };
};So Node supports the preload/import pattern cleanly, while Bun forces the middleware to own initialization.
Expected behavior
It would be helpful if Bun could support the same shape as Node, for example one of:
@sentry/hono/bunmiddleware does not callinit()when Sentry is already initialized.- A Bun middleware export exists that only installs the Hono request/response handling and app patches, assuming initialization happened in
instrument.ts. - The Bun docs explicitly recommend either preload initialization or middleware-owned initialization, but not both, and show the correct Hono + Bun setup.
- Hono route naming uses the route that actually handled the request, rather than
routePath(context, -1), so mounted wildcard middleware does not collapse transactions intoGET /*.
Workaround
We currently have to wrap the Bun middleware behavior locally:
- initialize Sentry in
instrument.tsthrough Bun preload - avoid calling exported
Sentry.sentry(app, options)because it callsinit()again - import/use the shipped Hono
requestHandler,responseHandler, and app patching code from the installed package internals - guard initialization with
if (!Sentry.isInitialized()) - after
await next(), correct the active/root span name usingroutePath(context)instead ofroutePath(context, -1)
That works, but it relies on internal package paths because the shared Hono handlers are not exported as public API.
Metadata
Metadata
Assignees
Projects
- StatusShow more project fieldsNo status
Activity
linear-code commentedon May 26, 2026
JS-2614
s1gr1d commentedon May 27, 2026
Hello, the
@sentry/hono/bunsetup only requires setting up the middleware. Check out the specific docs for Hono and Bun here: https://docs.sentry.io/platforms/javascript/guides/hono/Does that work for you?
You don't need an external file with
--preload(bun does not supportmodule.registerHooks()which would register the preloaded module customization hooks in Node).module.registerHooksoven-sh/bun#273694 remaining items
s1gr1d commentedon May 27, 2026
The link you posted are the docs for
@sentry/bunnot@sentry/hono, which SDK do you use?And what do you exactly mean with "would it catch Drizzle"?
E.g. this "no such table: users" error should be captured:
itsjamie commentedon May 27, 2026
I'm using the Hono SDK, but with the Bun runtime dependency.
I was wondering about the automatic instrumentation for traces with regards to the Drizzle comment.
I guess I'm a little confused why the Hono SDK using Bun does the init for you, and you say doesn't support the autoinstrumentation.
But the Bun SDK docs say to do the --preload instrument file so you get autoinstrumentation, but warns that if you do a
bun buildit'll break. It seems counter-intuitive.It sounds like the correct answer for me is to remove the --preload file, and switch to just using the hono SDK?
feat(hono): Add warning in Bun for double init (#21195)
s1gr1d commentedon May 28, 2026
I know this might be a bit confusing. The thing is that auto-instrumentation for third-party libraries like postgresql works with module customization hooks (a Node thing) in ESM. However, those module customization hooks do not work in Bun (that's a runtime limitation). And yes, the docs say you should add it with
--preloadbut in Bun this would not make a difference.However, we are moving to using tracing channels, which will make it possible to see Bun traces.
Read more here if you are interested: https://blog.sentry.io/observability-with-tracing-channels/
Yes
itsjamie commentedon May 28, 2026
It's honestly outright misleading (the Bun documentation) and should be fixed, but there is no issue related here.