Skip to content

fix(hono): preserve symbol-keyed metadata on wrapped middleware handlers#20953

Open
ahmadio wants to merge 1 commit into
getsentry:developfrom
ahmadio:fix/hono-preserve-handler-symbol-metadata
Open

fix(hono): preserve symbol-keyed metadata on wrapped middleware handlers#20953
ahmadio wants to merge 1 commit into
getsentry:developfrom
ahmadio:fix/hono-preserve-handler-symbol-metadata

Conversation

@ahmadio
Copy link
Copy Markdown

@ahmadio ahmadio commented May 17, 2026

Summary

@sentry/hono's wrapMiddlewareWithSpan replaces a Hono middleware handler with a sentryTracedMiddleware closure but does not copy own-symbol properties from the original handler. Frameworks that attach metadata to a handler via a Symbol key (the immediate motivating case is hono-openapi's describeRoute, which sets Symbol("openapi")) lose that metadata at the point Sentry wraps the handler.

Downstream effect for rhinobase/hono-openapi: generateSpecs(app) walks app.routes, finds sentryTracedMiddleware closures with no Symbol("openapi") set, and returns { paths: {} }. The OpenAPI JSON / Scalar UI renders empty.

markFunctionWrapped(wrapped, handler) already 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.

Reproduction (before this fix)

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 registered = app.routes.find(r => r.path === '/test').handler
console.log(Object.getOwnPropertySymbols(registered))
// Before: []
// After:  [ Symbol(test-meta) ]

Change

Copy own-symbol properties from handler to wrapped after markFunctionWrapped, via Object.defineProperty with the source descriptor:

for (const sym of Object.getOwnPropertySymbols(handler)) {
  const descriptor = Object.getOwnPropertyDescriptor(handler, sym);
  if (descriptor) {
    Object.defineProperty(wrapped, sym, descriptor);
  }
}

Notes on the choice:

  • Limited to symbols, not string keys. String-named properties like displayName could collide with intentional fields on the wrapper, and no framework I'm aware of attaches handler metadata via string keys. Happy to expand if maintainers prefer.
  • Object.defineProperty rather than direct assignment so we set an own property on the wrapper instead of going through the prototype's accessors. This avoids TypeError: Cannot assign to read only property on well-known symbols inherited from AsyncFunction.prototype (e.g. Symbol.toStringTag), which surface in practice on mocked handlers and Proxy-based handlers.

Test plan

  • New unit test in packages/hono/test/shared/patchAppUse.test.ts: registers a handler carrying Symbol("test-meta") via app.use('/test', handler) and asserts the symbol is readable on app.routes[i].handler after wrapping.
  • yarn test in packages/hono passes (109/109)
  • yarn build:dev:filter @sentry/hono succeeds
  • yarn format:check clean

Reproduced against

  • @sentry/hono: 10.53.1
  • @sentry/node: 10.53.1
  • hono: 4.12.18 / 4.12.19
  • Node.js: 22.x / 24.15.0

Notes

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.

`wrapMiddlewareWithSpan` returned a new closure without copying own-symbol
properties from the original handler, so frameworks that attach metadata via
`Symbol` keys (e.g. `hono-openapi`'s `Symbol("openapi")` set by `describeRoute`)
lost it as soon as Sentry wrapped the handler. Downstream consumers that walk
`app.routes[i].handler` then saw no metadata, producing empty OpenAPI specs.

Use `Object.defineProperty` with the source descriptor so we set an own
property on the wrapper without tripping over read-only well-known symbols on
the function's prototype (e.g. `Symbol.toStringTag` on `AsyncFunction`).
@ahmadio ahmadio requested a review from a team as a code owner May 17, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant