Summary
On React Native + Hermes (including Expo OTA / expo export Hermes .hbc bundles), plain JS exception events often ship without debug_meta.images, even when:
- Metro has injected the Debug ID premodule (
_sentryDebugIds / _sentryDebugIdIdentifier)
- The matching artifact bundle was uploaded with that Debug ID (
sentry-expo-upload-sourcemaps / @sentry/expo-upload-sourcemaps, Release: None)
Profiling already solves this correctly via getDebugMetadata() in dist/js/profiling/debugid.js, which reads GLOBAL_OBJ._sentryDebugIds directly and stamps code_file as app:///index.android.bundle / app:///main.jsbundle. That helper is not wired into the exception event pipeline.
Why it matters
Without debug_meta, backends fall back to release + filename matching. Expo/RN rewrites every JS frame to the same canonical filename (app:///index.android.bundle / app:///main.jsbundle) via RewriteFrames. OTA updates keep the native release, so symbolication can apply the native build’s Hermes map to OTA bytecode → wrong files/lines.
This is especially painful on Sentry-compatible self-hosts, but the missing debug_meta is an SDK-side gap regardless of backend.
Observed with @sentry/react-native 8.19.0, Expo Router, Hermes, OTA via artifact bundles.
Root cause (SDK)
Exception events rely on @sentry/core prepareEvent → applyDebugIds → getFilenameToDebugIdMap:
- Take the single key in
_sentryDebugIds (the Error().stack string captured when the Debug ID premodule ran)
- Re-parse that stack with the generic stack parser
- Require an exact
filename string match against exception frames
That path is fragile for Hermes top-level/premodule stacks. If the reparse yields no usable filename (or a different shape than real exception frames), filenameDebugIdMap is empty → applyDebugMeta no-ops → no debug_meta on the event.
By contrast, profiling:
// profiling/debugid.ts (conceptually)
const debugId = Object.values(GLOBAL_OBJ._sentryDebugIds)[0]
return [{ type: 'sourcemap', code_file: DEFAULT_BUNDLE_NAME, debug_id: debugId }]
No stack reparse. One bundle, one Debug ID, fixed code_file.
Suggested fix
Reuse the profiling approach for all events:
- Extract / share
getDebugMetadata() outside profiling
- Add a default integration (or extend an existing one) that stamps
event.debug_meta.images from _sentryDebugIds (+ _sentryDebugIdIdentifier fallback) using ANDROID_DEFAULT_BUNDLE_NAME / IOS_DEFAULT_BUNDLE_NAME
- Keep it idempotent if core
applyDebugIds already succeeded
This matches how RN actually works (single Hermes bundle per platform) and makes Expo OTA Debug ID matching reliable without release/dist workarounds.
Reproduction sketch
- Expo app with
getSentryExpoConfig + @sentry/react-native/expo plugin
- Publish an OTA /
expo export Hermes bundle; upload with npx @sentry/expo-upload-sourcemaps dist (artifact bundle, Debug ID in upload report)
- On a device running that OTA (
Updates.isEmbeddedLaunch === false), Sentry.captureException(new Error('probe'))
- Inspect event JSON:
debug_meta often missing; release still native (*.androidapp@x.y.z+build); frames use app:///index.android.bundle
Workaround we use today
App-level Sentry.addEventProcessor that mirrors profiling’s getDebugMetadata() and attaches debug_meta.images before send. Happy to upstream that if maintainers agree on the approach.
Related
- Expo docs assume Debug ID path “just works” after
sentry-expo-upload-sourcemaps (Using Sentry)
- Metro Debug ID injection is fine; upload Debug IDs match; the break is event emission
Summary
On React Native + Hermes (including Expo OTA /
expo exportHermes.hbcbundles), plain JS exception events often ship withoutdebug_meta.images, even when:_sentryDebugIds/_sentryDebugIdIdentifier)sentry-expo-upload-sourcemaps/@sentry/expo-upload-sourcemaps,Release: None)Profiling already solves this correctly via
getDebugMetadata()indist/js/profiling/debugid.js, which readsGLOBAL_OBJ._sentryDebugIdsdirectly and stampscode_fileasapp:///index.android.bundle/app:///main.jsbundle. That helper is not wired into the exception event pipeline.Why it matters
Without
debug_meta, backends fall back to release + filename matching. Expo/RN rewrites every JS frame to the same canonical filename (app:///index.android.bundle/app:///main.jsbundle) viaRewriteFrames. OTA updates keep the nativerelease, so symbolication can apply the native build’s Hermes map to OTA bytecode → wrong files/lines.This is especially painful on Sentry-compatible self-hosts, but the missing
debug_metais an SDK-side gap regardless of backend.Observed with
@sentry/react-native8.19.0, Expo Router, Hermes, OTA via artifact bundles.Root cause (SDK)
Exception events rely on
@sentry/coreprepareEvent→applyDebugIds→getFilenameToDebugIdMap:_sentryDebugIds(theError().stackstring captured when the Debug ID premodule ran)filenamestring match against exception framesThat path is fragile for Hermes top-level/premodule stacks. If the reparse yields no usable filename (or a different shape than real exception frames),
filenameDebugIdMapis empty →applyDebugMetano-ops → nodebug_metaon the event.By contrast, profiling:
No stack reparse. One bundle, one Debug ID, fixed
code_file.Suggested fix
Reuse the profiling approach for all events:
getDebugMetadata()outside profilingevent.debug_meta.imagesfrom_sentryDebugIds(+_sentryDebugIdIdentifierfallback) usingANDROID_DEFAULT_BUNDLE_NAME/IOS_DEFAULT_BUNDLE_NAMEapplyDebugIdsalready succeededThis matches how RN actually works (single Hermes bundle per platform) and makes Expo OTA Debug ID matching reliable without release/dist workarounds.
Reproduction sketch
getSentryExpoConfig+@sentry/react-native/expopluginexpo exportHermes bundle; upload withnpx @sentry/expo-upload-sourcemaps dist(artifact bundle, Debug ID in upload report)Updates.isEmbeddedLaunch === false),Sentry.captureException(new Error('probe'))debug_metaoften missing;releasestill native (*.androidapp@x.y.z+build); frames useapp:///index.android.bundleWorkaround we use today
App-level
Sentry.addEventProcessorthat mirrors profiling’sgetDebugMetadata()and attachesdebug_meta.imagesbefore send. Happy to upstream that if maintainers agree on the approach.Related
sentry-expo-upload-sourcemaps(Using Sentry)