fix(sourcemap): adopt a debug ID already present on the sourcemap - #1496
Merged
Conversation
`inject` and `upload` only looked for an existing debug ID in the JS file (`//# debugId=`). When the JS had no such comment but the sourcemap already carried `debug_id`/`debugId`, the CLI minted a new content-derived ID, rewrote the JS, and overwrote the map's field. That breaks Sentry's bundler plugins running with `sourcemaps.disable: 'disable-upload'`. Those builds inject a runtime `_sentryDebugIds` snippet and stamp the same ID onto the emitted `.js.map`, but deliberately leave the bundle without a `//# debugId=` comment — rewriting it after emit invalidates subresource-integrity hashes computed during the build (getsentry/sentry-javascript-bundler-plugins#949). A later `sentry sourcemap upload ./dist` minted a different ID, so what the SDK reported at runtime never matched what Sentry indexed. Debug ID precedence is now: 1. `//# debugId=` in the JS (unchanged; the on-disk spec marker) 2. a valid `debug_id`/`debugId` on the sourcemap — adopted as-is 3. otherwise, mint from content (unchanged) In case 2 neither file is written: no IIFE snippet (the bundle already has the plugin's own `_sentryDebugIds` writer, and a second one under a different stack key makes the runtime mapping ambiguous) and no `mappings` offset (the plugin's map already lines up with the un-offset bundle). `debug_id` wins over `debugId`; a value that isn't a well-formed UUID is treated as absent and falls through to minting. Applies to external and inline maps, to `--dry-run`, and to the discovery read behind `sourcemap resolve`. Default behavior, matching v3 — not gated behind a flag, and `--no-rewrite` is unchanged. Note this is slightly stricter than v3's `sourcemaps inject`, which adopted the map's ID but still added its snippet to the bundle and re-serialized the map (its "Ignored: … already have debug ids" report meant "no new ID minted", not "file untouched"). Leaving both files byte-identical is what makes the integrity-hash workflow above work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
BYK
marked this pull request as ready for review
August 27, 2026 13:10
BYK
reviewed
Aug 27, 2026
BYK
reviewed
Aug 27, 2026
BYK
approved these changes
Aug 28, 2026
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
sentry sourcemap injectandsentry sourcemap uploadonly looked for an existing debug ID in the JS file (//# debugId=). If the JS had no such comment but the sourcemap already carrieddebug_id/debugId, the CLI minted a brand-new content-derived ID, rewrote the JS, and overwrote the map's field.Why it matters
Sentry's JavaScript bundler plugins support
sourcemaps.disable: 'disable-upload': the plugin injects a runtime_sentryDebugIdssnippet into the bundle during the build and stamps that same ID into the emitted.js.map(getsentry/sentry-javascript#23619). It deliberately does not append//# debugId=to the bundle, because rewriting the bundle after emit invalidates subresource-integrity hashes computed during the build (getsentry/sentry-javascript-bundler-plugins#949 — the reporter serves<script integrity="…">fromwebpack-assets-manifest).Those users then run
sentry sourcemap upload ./dist. Before this change the CLI minted a different ID, so the ID the SDK reported at runtime never matched what Sentry indexed and nothing symbolicated — plus the rewrite broke the SRI hashes they were protecting.What changed
Debug ID precedence when resolving a file pair:
//# debugId=in the JS → use it (unchanged; it's the on-disk spec marker)debug_id/debugIdon the sourcemap → adopt it,wasInjected: false, neither file is writtendebug_idwins overdebugId. A value that isn't a well-formed UUID is treated as absent and falls through to minting.Case 2 deliberately does not prepend the IIFE snippet and does not offset
mappings: the bundle already has the plugin's own_sentryDebugIdswriter (a second one under a different stack key makes the runtime mapping ambiguous), and the plugin's map already lines up with the un-offset bundle.Covers external maps, inline (base64
data:) maps,--dry-run, and the discovery read behindsourcemap resolve. This is default behavior — not gated behind a new flag, and--no-rewriteis unchanged.src/lib/sourcemap/debug-id.ts— newreadSourcemapDebugId(); early return ininjectDebugId()andinjectInlineDebugId()src/lib/sourcemap/inject.ts— newreadMapDebugId()helper wired into the--dry-runbranch andresolveDirectorySourcemaps()The upload path needed no change —
buildArtifactPair()already puts the resolveddebugIdon both entries — but it's covered end to end by new tests rather than assumed.On v3 parity
Worth recording, since the flag list in the migration guide reads as a capability loss:
--debug-id-referencewas anuploadflag, and narrower than it sounds. Its help text: "By default Debug ID reference has to be present both in the source and the related sourcemap. But in cases of binary bundles, the tool can't verify presence of the Debug ID. This flag allows use of Debug ID from the linked sourcemap." It's a verification relaxation for binary bundles (Hermes bytecode, where a//# debugId=comment is impossible) — it injects nothing. This is why it shows up in the React Native docs right aftercopy-debugid.js.sourcemaps injectalready adopted the map's ID by default, no flag — but it still rewrote both files. Thedebug_id_freshboolean insourcemaps.rsonly selects a report bucket;fixup_js_file()still injects the snippet and shifts the mappings either way. SoIgnored: The following sourcemap files already have debug idsmeant "no new ID minted", not "file untouched".So this PR matches v3 on which ID wins and is intentionally stricter on what gets written — leaving both files byte-identical is precisely what makes the integrity-hash workflow work.
Known gap, flagged for review: a map carrying a
debug_idwhose bundle has neither a comment nor a plugin snippet now gets no runtime registration at all (v3 would have injected one). It doesn't affect the two motivating workflows — Hermes can't be injected anyway, anddisable-uploadalready has the snippet — but if we want v3 parity there, the fix would be to detect_sentryDebugIdIdentifierin the bundle and fall back to injecting when absent. Left out because sniffing "does this bundle self-register?" from a substring is fragile.Tests
test/lib/sourcemap/inject.test.ts— 8 new cases: externaldebug_id; thedebugIdspelling; precedence between the two; inline map; JS comment wins over a conflicting map field; malformed value falls through to minting;--dry-runreports the adopted ID; repeat runs stay a no-op. Each asserts the JS and map are byte-identical.test/commands/sourcemap/upload.test.ts— 2 new cases asserting uploaded artifacts carry the adopted ID on both theminified_sourceandsource_mapentries, for external and inline maps.Docs
Added a bullet to
migrating-from-v3.mdnoting the capability is back as automatic default behavior, with the accurate v3 semantics.Verification
pnpm lint— cleanpnpm typecheck— cleanpnpm test:unit— 9334 passed. The 6 failures intest/lib/time-range.test.tsare pre-existing and unrelated (timezone-dependent assertions); confirmed identical on a clean tree viagit stash.🤖 Generated with Claude Code