Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/node
SDK Version
10.53.1
Framework Version
@sentry/hono 10.53.1
Link to Sentry event
No response
Reproduction Example/SDK Setup
@sentry/hono's wrapMiddlewareWithSpan (called by patchAppUse + wrapSubAppMiddleware) replaces a Hono middleware handler with a sentryTracedMiddleware wrapper but does not copy own-symbol properties from the original handler. Any framework that attaches metadata to a handler via a Symbol key - hono-openapi's describeRoute is the immediate one - loses that metadata at the point Sentry wraps the handler.
The downstream effect for rhinobase/hono-openapi: generateSpecs(app) walks app.routes, finds sentryTracedMiddleware closures with no Symbol(openapi) set, and returns { paths: {} }. Scalar / /openapi.json renders an empty spec.
Reproduction Example/SDK Setup
// index.mjs
import { Hono } from 'hono'
import { sentry } from '@sentry/hono/node'
const META = Symbol('test-meta')
const baseApp = new Hono()
const app = baseApp.use('*', sentry(baseApp))
const handler = async (_c, next) => next()
handler[META] = { example: 'should survive Sentry wrapping' }
app.use('/test', handler)
const testRoute = (app.routes ?? []).find((r) => r.path === '/test')
const symbols = Object.getOwnPropertySymbols(testRoute.handler)
const hasMeta = symbols.some((s) => s.description === 'test-meta')
console.log({
hasSentryOriginal: '__sentry_original__' in testRoute.handler,
symbolsOnHandler: symbols.map((s) => s.description),
})
console.log(hasMeta ? '✅ symbol survived' : '❌ symbol stripped — bug reproduced')
SDK / package versions used:
@sentry/hono: 10.53.1
@sentry/node: 10.53.1
hono: 4.12.19 (also reproduces against 4.12.18)
- Node.js: 24.15.0 (also reproduces against 22.x — not Node-version-dependent)
Steps to Reproduce
mkdir sentry-hono-symbol-repro && cd sentry-hono-symbol-repro
npm init -y
npm pkg set type=module
npm install hono@^4.12 @sentry/hono@^10.53 @sentry/node@^10.53
Creating and adding the index.mjs content mentioned above.
Expected Result
Symbol(test-meta) is still readable on the registered handler — Sentry's wrapper preserves framework-attached symbol metadata so downstream tools like hono-openapi continue to see it:
{ hasSentryOriginal: true, symbolsOnHandler: [ 'test-meta' ] }
✅ symbol survived
Actual Result
The wrapper carries __sentry_original__ (so the original is recoverable) but does not carry any of the handler's symbol properties, so every downstream consumer that reads Object.getOwnPropertySymbols(handler) sees nothing:
{ hasSentryOriginal: true, symbolsOnHandler: [] }
❌ symbol stripped — bug reproduced
Additional Context
Root cause
packages/hono/src/shared/wrapMiddlewareSpan.ts (10.53.1 build at build/esm/shared/wrapMiddlewareSpan.js):
function wrapMiddlewareWithSpan(handler) {
if (getOriginalFunction(handler)) return handler
const wrapped = async function sentryTracedMiddleware(context, next) { /* … */ }
markFunctionWrapped(wrapped, handler)
return wrapped
}
The new function has no Symbol(foo) properties inherited from handler. patchAppUse's proxy maps every handler (including the (path, ...handlers) form used in the repro) through wrapMiddlewareWithSpan, so the symbol-stripped wrapper is exactly what ends up on app.routes[i].handler and what downstream consumers walk.
markFunctionWrapped(wrapped, handler) sets __sentry_original__ so the wrapper is unwrappable in principle, but the wrapper itself doesn't carry the framework's metadata, which is what every other consumer reads.
Suggested fix
Two-line patch — copy own-symbol properties to the wrapper after markFunctionWrapped:
const wrapped = async function sentryTracedMiddleware(context, next) { /* … */ }
markFunctionWrapped(wrapped, handler)
+ // Preserve framework-attached symbol metadata (e.g., hono-openapi's
+ // Symbol("openapi"), which downstream tooling reads off the handler).
+ for (const sym of Object.getOwnPropertySymbols(handler)) {
+ wrapped[sym] = handler[sym]
+ }
return wrapped
Same change should be considered for any other wrapper that returns a NEW function while keeping the original referenceable via __sentry_original__.
Workaround in user code
Until this is fixed upstream, projects using hono-openapi + @sentry/hono together can snapshot+swap-and-restore around the spec generation:
let cache = null
async function generateSpecsAroundSentry() {
if (cache) return cache
const swapped = []
for (const route of app.routes) {
const original = route.handler?.__sentry_original__
if (original) {
swapped.push([route, route.handler])
route.handler = original
}
}
try {
cache = await generateSpecs(app, { documentation })
return cache
} finally {
for (const [route, wrapped] of swapped) route.handler = wrapped
}
}
There's a short race window during the swap (a concurrent request hits the unwrapped handler → no Sentry span for that one request) which we mitigate by caching after first call + pre-warming at boot.
Related
Related issue surface but distinct from #20449 (route groups dropping app.use patches). This one is about the patched app.use succeeding but the wrapper losing handler-level symbol metadata.
Priority
No response
Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/node
SDK Version
10.53.1
Framework Version
@sentry/hono 10.53.1
Link to Sentry event
No response
Reproduction Example/SDK Setup
@sentry/hono'swrapMiddlewareWithSpan(called bypatchAppUse+wrapSubAppMiddleware) replaces a Hono middleware handler with asentryTracedMiddlewarewrapper but does not copy own-symbol properties from the original handler. Any framework that attaches metadata to a handler via aSymbolkey -hono-openapi'sdescribeRouteis the immediate one - loses that metadata at the point Sentry wraps the handler.The downstream effect for
rhinobase/hono-openapi:generateSpecs(app)walksapp.routes, findssentryTracedMiddlewareclosures with noSymbol(openapi)set, and returns{ paths: {} }. Scalar //openapi.jsonrenders an empty spec.Reproduction Example/SDK Setup
SDK / package versions used:
@sentry/hono: 10.53.1@sentry/node: 10.53.1hono: 4.12.19 (also reproduces against 4.12.18)Steps to Reproduce
Creating and adding the index.mjs content mentioned above.
Expected Result
Symbol(test-meta)is still readable on the registered handler — Sentry's wrapper preserves framework-attached symbol metadata so downstream tools likehono-openapicontinue to see it:Actual Result
The wrapper carries
__sentry_original__(so the original is recoverable) but does not carry any of the handler's symbol properties, so every downstream consumer that readsObject.getOwnPropertySymbols(handler)sees nothing:Additional Context
Root cause
packages/hono/src/shared/wrapMiddlewareSpan.ts(10.53.1 build atbuild/esm/shared/wrapMiddlewareSpan.js):The new function has no
Symbol(foo)properties inherited fromhandler.patchAppUse's proxy maps every handler (including the(path, ...handlers)form used in the repro) throughwrapMiddlewareWithSpan, so the symbol-stripped wrapper is exactly what ends up onapp.routes[i].handlerand what downstream consumers walk.markFunctionWrapped(wrapped, handler)sets__sentry_original__so the wrapper is unwrappable in principle, but the wrapper itself doesn't carry the framework's metadata, which is what every other consumer reads.Suggested fix
Two-line patch — copy own-symbol properties to the wrapper after
markFunctionWrapped:const wrapped = async function sentryTracedMiddleware(context, next) { /* … */ } markFunctionWrapped(wrapped, handler) + // Preserve framework-attached symbol metadata (e.g., hono-openapi's + // Symbol("openapi"), which downstream tooling reads off the handler). + for (const sym of Object.getOwnPropertySymbols(handler)) { + wrapped[sym] = handler[sym] + } return wrappedSame change should be considered for any other wrapper that returns a NEW function while keeping the original referenceable via
__sentry_original__.Workaround in user code
Until this is fixed upstream, projects using
hono-openapi+@sentry/honotogether can snapshot+swap-and-restore around the spec generation:There's a short race window during the swap (a concurrent request hits the unwrapped handler → no Sentry span for that one request) which we mitigate by caching after first call + pre-warming at boot.
Related
Related issue surface but distinct from #20449 (route groups dropping
app.usepatches). This one is about the patchedapp.usesucceeding but the wrapper losing handler-level symbol metadata.Priority
No response