Skip to content

feat(sourcemap): add wasm sourcemap support for inject and upload - #1588

Draft
d2anamaria wants to merge 1 commit into
ana/feat/wasm/debug-file-pipelinefrom
ana/feat/wasm/sourcemaps
Draft

d2anamaria wants to merge 1 commit into
ana/feat/wasm/debug-file-pipelinefrom
ana/feat/wasm/sourcemaps

Conversation

@d2anamaria

Copy link
Copy Markdown

Problem

sentry sourcemap inject and sentry sourcemap upload only discover .js, .cjs, and .mjs files. Emscripten built with -gsource-map emits app.wasm alongside app.wasm.map, a Source Map v3 JSON file. Discovery never sees the .wasm.map, so it is never uploaded.

Even when a .wasm.map reaches Sentry by other means, nothing connects it to the module it describes. Sentry identifies a wasm module by the build_id custom section, which events report as debug_meta.images[].debug_id. Sourcemap uploads are matched by the debug_id field inside the map JSON. Those two identifiers were never tied together, so the map could not join its module.

Adding .wasm to the default extension set does not solve this. The JavaScript path mutates files as text: it reads them as UTF-8, prepends a runtime snippet, appends a //# debugId= comment, and offsets the map's mappings to match. The first three corrupt a binary module, the fourth corrupts a map that has no lines to offset, and none of them is needed because a wasm module already carries its identity in build_id. Hashing file content for the debug ID, as the JS path does, would mint an identifier unrelated to the one events actually report.

Solution

Both commands now process wasm pairs alongside JavaScript pairs. A directory with no wasm behaves exactly as before.

For each app.wasm + app.wasm.map pair, the module's build_id is the source of truth. It is the identifier the running module reports to Sentry. The CLI reads it, formats it as a canonical dashed UUID, and writes that value onto the map as debug_id and debugId. A module that has no build_id yet is stamped with a freshly generated random UUID first.

The operation is idempotent. When the map already carries the matching id, neither file is touched, so re-running the command leaves the build output unchanged. When the map carries a different id, the module's id wins and the map is corrected — the map describes the module, not the other way round.

upload sends the pair through the existing artifact-bundle path: the .wasm as minified_source, the .wasm.map as source_map, both keyed by the same debug ID. --url-prefix, --strip-prefix, --strip-common-prefix, --release, and --dist all apply.

The module is never treated as text.

*.debug.wasm files are skipped. Those are DWARF companions belonging to debug-files prepare, uploaded through the debug-file pipeline instead.

No new flags. --dry-run reports the debug ID each pair would get and writes nothing. --no-rewrite stamps nothing but still uploads under a build_id already on disk. --allow-empty counts JS and wasm pairs together, so a wasm-only directory does not trip the "no .map files" error. --ignore and --ignore-file apply to wasm discovery. --ext stays JavaScript-only; wasm discovery is fixed to .wasm.

When a directory holds .wasm files with no maps beside them, the empty-discovery error now points at -gsource-map instead of Vite and webpack settings. Command output lists reconciled wasm pairs alongside JS pairs.

Limitations

Pair discovery

Pairing is by filename convention (<stem>.wasm<stem>.wasm.map). The module's sourceMappingURL custom section is not parsed, so a build that renames its map or writes it to another directory is skipped.

Motivation: the section normally holds a deployment URL, set from Emscripten's --source-map-base, which does not locate the file on disk. Resolving it correctly would also require classifying remote URLs and guarding against paths that escape the upload directory — work that yields nothing over the default layout.

Inline sourcemaps

Inline (data-URL) wasm sourcemaps are not supported.

Motivation: no mainstream toolchain emits them. Supporting the form would require decoding the custom section, re-encoding the map, and rewriting the module's bytes, with no standalone file to upload.

Out of scope

  • The CLI does not generate sourcemaps, for wasm any more than for JavaScript. The build must emit them.
  • A build_id shorter than 16 bytes cannot form a UUID. Such a pair is left untouched rather than stamped with a malformed id.

Notes for reviewers

Discovery now walks the target directory twice: once for JavaScript, once for .wasm. The walks run sequentially, not concurrently. The added pass is cheap — it filters on extension, opens no files, and only stats a candidate map, but it is a second traversal. Flagging it in case anyone wants it handled differently.

sourcemap inject --json now emits a "wasm" key listing the reconciled wasm pairs. It appears as an empty array for JavaScript-only directories, so the JSON shape changes slightly even when no wasm is involved. Nothing consumes the field today.


This change aligns the identifiers so the uploaded artifacts are correct. It does not assume Symbolicator symbolicates wasm frames from source maps today.

- Process wasm + map pairs alongside JS in inject and upload by default
- Derive map debug IDs from the module build_id instead of content hash
- Upload wasm and map as a matched artifact pair through existing API
- Improve empty-directory errors when wasm modules lack companion maps
@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cli Ready Ready Preview Sep 15, 2026 1:37pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Sep 15, 2026 1:37pm UTC

Request Review

@d2anamaria
d2anamaria marked this pull request as draft September 15, 2026 13:37
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Sep 15, 2026
const absDir = resolvePath(dir);
let wasmFiles = 0;
let wasmMaps = 0;
for await (const wasmPath of walkWasmModules(absDir)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The addWasmDiscoveryCounts function incorrectly counts ignored .wasm files, leading to a misleading error message about missing sourcemaps when files are intentionally excluded.
Severity: MEDIUM

Suggested Fix

Update the addWasmDiscoveryCounts function to accept an ignoreMatcher parameter. Pass this matcher to the walkWasmModules call to ensure it respects ignored files, consistent with discoverWasmPairs. The callers in upload.ts and inject.ts will also need to be updated to pass the matcher.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: packages/cli/src/lib/sourcemap/wasm.ts#L202

Potential issue: The `addWasmDiscoveryCounts` function does not respect ignore patterns
(from `.gitignore` or `--ignore`) because it fails to pass the `ignoreMatcher` to its
call to `walkWasmModules`. In contrast, the `discoverWasmPairs` function does pass the
matcher. This discrepancy causes incorrect diagnostics when all WebAssembly files in a
directory are ignored. The system will report that it found `.wasm` files but no
corresponding `.wasm.map` files, incorrectly advising the user to recompile with
`-gsource-map` when the actual issue is that the files are being ignored.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment on lines +203 to +205
wasmFiles += 1;
if (await hasCompanionMap(`${wasmPath}.map`)) {
wasmMaps += 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Orphaned .wasm.map files are misclassified as JavaScript sourcemaps, resulting in a misleading error message that points to missing JS files instead of missing .wasm modules.
Severity: LOW

Suggested Fix

Adjust the diagnostic counting logic to correctly handle orphaned .wasm.map files. This could be done by ensuring .wasm.map files are not included in the initial JavaScript sourcemap count, or by modifying addWasmDiscoveryCounts to account for them properly, preventing them from being misreported.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: packages/cli/src/lib/sourcemap/wasm.ts#L203-L205

Potential issue: A logic flaw in diagnostic counting can produce a misleading error
message when a directory contains an orphaned `.wasm.map` file without a corresponding
`.wasm` file. The initial scan in `diagnoseEmptyDiscovery` counts all files ending in
`.map`, including the orphaned `.wasm.map`. However, the subsequent
`addWasmDiscoveryCounts` function only subtracts map counts for wasm maps that have a
companion `.wasm` file. As a result, the orphaned `.wasm.map` is incorrectly categorized
as a JavaScript sourcemap, leading to an error message stating "Found N .map file(s) ...
but no companion JS files."

Did we get this right? 👍 / 👎 to inform future reviews.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d27184b. Configure here.

/** Whether a wasm pair was changed on disk. */
function wasmChanged(result: WasmSyncResult): boolean {
return result.mapWritten || result.moduleStamped;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dry-run marks wasm pairs unchanged

Medium Severity

--dry-run never sets mapWritten or moduleStamped, so wasmChanged stays false. Every wasm pair then counts as skipped and renders with a dash, even when the map would be stamped or the module would get a build_id. JavaScript pairs on the same dry-run still show as modified when they would change.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d27184b. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant