Skip to content

[@sentry/hono] sentry() middleware breaks .get() routes — Scalar UI and openapi.json return empty/blank #21019

@salmomascarenhas

Description

@salmomascarenhas

SDK Setup

// instrumentation.ts
import * as Sentry from '@sentry/hono/node'
Sentry.init({ dsn: '...', tracesSampleRate: 1.0 })

// api.ts
import { sentry } from '@sentry/hono'
import { Hono } from 'hono'
import { openAPIRouteHandler } from 'hono-openapi'
import { Scalar } from '@scalar/hono-api-reference'

const app = new Hono()
  .use(sentry(app as unknown as Hono)) // cast required — TS rejects Hono<CustomEnv>
  .basePath('/api')
  .use('/*', authMiddleware)
  .route('/', someRoute)
  .onError(handleError)

app.get('/openapi.json', openAPIRouteHandler(app, openApiConfig))
app.get('/docs', Scalar(scalarConfig))

Steps to Reproduce

  1. Create a Hono app with .basePath(), .route(), and .use() registered routes.
  2. Add .get() routes for API reference (Scalar or Swagger) and /openapi.json.
  3. Mount sentry() as the first middleware (before .basePath()).
  4. Initialize Sentry via @sentry/hono/node.
  5. Visit /docs or /openapi.json.

Expected Result

  • /docs renders the Scalar UI with all registered routes and their schemas.
  • /openapi.json returns the full OpenAPI spec.

Actual Result

  • /docs renders a blank Scalar shell with no routes listed.
  • /openapi.json returns an empty or minimal spec (routes missing).
  • All .route()-registered endpoints respond correctly.
  • Removing sentry() from the chain immediately restores correct behavior.

Additional Context

Environment

  • @sentry/hono: 10.53.1
  • hono: 4.x
  • Node.js: 24.x, ESM, loaded via node --import=./dist/instrumentation.js
  • @scalar/hono-api-reference + hono-openapi
  • Deployment: Cloud Run

Analysis of applyPatches

sentry() calls applyPatches(app) which applies two patches:

  1. patchAppUse — wraps app.use via Proxy to inject OTel spans into middleware handlers.
  2. installRouteHookOnPrototype — patches HonoBase.prototype.route globally (on the prototype, not the instance) to wrap sub-app middleware.

Neither patch explicitly targets .get(). The Scalar UI and /openapi.json are registered via .get(), so they should be unaffected. Yet removing sentry() is the only change needed to restore them.

Hypotheses

H1 — installRouteHookOnPrototype prototype mutation:
Patching HonoBase.prototype.route globally may corrupt the internal routing state of sub-apps created by .basePath() (which calls #clone()). The cloned app shares the same routes array reference as the parent; prototype-level wrapping may interfere with how Hono resolves .get() routes defined on the parent after a .basePath() chain.

H2 — ESM Symbol identity hazard:
@sentry/hono's requestHandler uses GET_MATCH_RESULT (a Symbol()) from hono/request/constants to access route match data via c.req[GET_MATCH_RESULT]. If the module graph resolves two separate instances of hono (one inside @sentry/hono's own deps, one in the app), the Symbol identities diverge and the lookup silently returns undefined, causing routePath() to produce incorrect route patterns — potentially breaking the route resolution pipeline for .get() routes.

TypeScript cast required

The sentry() function signature expects Hono (no generics), but Hono apps with custom env types (Hono<CtxEnv>) require app as unknown as Hono. This cast suggests the BETA type definitions are incomplete.

Workaround

Remove sentry() from the middleware chain. Errors continue to be captured via the onError handler using captureRequestError from @sentry/node (initialized via @sentry/hono/node).

Metadata

Metadata

Assignees

No fields configured for issues without a type.

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions