feat(wasm-debug-files): Add prepare command for WASM debug setup - #3403
feat(wasm-debug-files): Add prepare command for WASM debug setup#3403d2anamaria wants to merge 5 commits into
Conversation
…load - Add debug-files prepare to automate WebAssembly debug setup for Sentry - Scan .wasm files or directories and classify each module's debug quality - Split modules with embedded DWARF into a deployable .wasm and a *.debug.wasm companion - Ensure stripped and companion artifacts share a build_id for matching in Sentry - Keep line-level debug info in the companion while removing it from the deploy artifact - Skip symtab-only modules with a warning when line-level symbolication is unavailable - Detect already-prepared pairs and avoid re-splitting them - Upload companions to Sentry by default, with options to split-only or dry-run - Support CI workflows that fail when expected DWARF debug info is missing
- Stamp a build_id into every inspected module, not only the ones that get split; dry runs stay read-only - Add --strip-names to drop the name section from the deployable while the companion keeps it - Strip the module in place when --out-dir is set, so the flag redirects only the companion - Skip uploads for symtab-only modules, whose name section stays readable in the deployable - Expose stripNames in the npm wrapper
Instructions and example for changelogPlease add an entry to Example: ## Unreleased
### Features
- Add prepare command for WASM debug setup ([#3403](https://github.com/getsentry/sentry-cli/pull/3403))If none of the above apply, you can opt out of this check by adding |
| let dwarf_missing = results | ||
| .iter() | ||
| .any(|result| result.action == PrepareAction::Skipped && !result.quality.has_dwarf()); |
There was a problem hiding this comment.
Bug: The --require-dwarf flag incorrectly succeeds when a WASM module has ExternalDebugInfo but its companion debug file is missing or mismatched, because has_dwarf() returns true.
Severity: MEDIUM
Suggested Fix
Modify the condition that sets dwarf_missing. The logic should consider cases where the action is Skipped and the quality is ExternalDebugInfo as an error condition when --require-dwarf is used. This combination indicates a missing or mismatched companion file that should not be ignored.
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: src/commands/debug_files/prepare.rs#L231-L233
Potential issue: The `--require-dwarf` flag is intended to cause an error if a WASM
module lacks DWARF information or a matching companion debug file. However, the current
logic only checks if a skipped module's quality `!has_dwarf()`. When a module has
`ExternalDebugInfo` but its companion file is missing or has a mismatched build ID, it
is correctly marked as `Skipped`. But since `has_dwarf()` returns `true` for
`ExternalDebugInfo`, the condition for an error is not met. This leads to the command
succeeding silently with just a warning, contrary to the flag's documented purpose.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9cd2f9d. Configure here.
| // JSON + strict checks | ||
| let dwarf_missing = results | ||
| .iter() | ||
| .any(|result| result.action == PrepareAction::Skipped && !result.quality.has_dwarf()); |
There was a problem hiding this comment.
--require-dwarf misses dangling debug refs
Medium Severity
--require-dwarf treats ExternalDebugInfo as having DWARF via has_dwarf(), so a skipped module with a dangling external_debug_info and no matching companion still passes the CI guard. That module cannot be symbolicated, but the command exits successfully.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9cd2f9d. Configure here.
| Some(dir) => dir.join(name), | ||
| None => wasm_path.with_file_name(name), | ||
| } | ||
| } |
There was a problem hiding this comment.
--out-dir collisions overwrite companions
Medium Severity
companion_path names companions only from the file stem when --out-dir is set. Two modules that share a filename in different directories write the same *.debug.wasm, so the later split overwrites the earlier companion and breaks build_id matching for the first module.
Reviewed by Cursor Bugbot for commit 9cd2f9d. Configure here.
|
Hey @d2anamaria, just wondering why you are opening this PR in this repo? This is the repo for the legacy Is there a concrete reason that this cannot be implemented in the new CLI? If not, please close this PR and implement the feature in the new CLI 🙏 |


Problem
WebAssembly needs manual work before Sentry can symbolicate crashes. Today users run
wasm-splitto inject abuild_idand split DWARF into a companion file, then runsentry-cli debug-files upload --type wasmon that companion. Two tools, two steps, easy to get wrong. Nothing warns you when a module has no usable debug info, so silent failures show up later as unsymbolicated stack traces.Solution
sentry-cli debug-files prepare <PATH>does the whole pipeline in one command.It scans for
.wasmmodules, classifies each one, splits the ones that have DWARF, and uploads the companions.Behavior
Every module the command inspects gets a
build_idif it has none, whether or not it can be split. Thebuild_idis how Sentry ties a module in a stack trace to its debug file, so a module without one can never be symbolicated — not even by a debug file uploaded later. Stamping up front keeps that door open.For each
.wasmmodule with embedded DWARF:build_idif the module has none.*.debug.wasmcompanion that keeps the Code section and DWARF..debug_*sections from the deployable.wasm, in place.external_debug_info.The stripped module stays at the original path, so your deploy artifact does not move. Both files carry the same
build_id, which is how Sentry matches a crash to its debug file.Outcomes
build_id--dry-runpreviewWarnings
A module without usable debug info does not stop the run. It is stamped and left otherwise untouched, and the command explains why it was not split:
no line-level symbolication (name/symtab only)no debug information; rebuild with DWARF (Emscripten -g, wasm-pack dwarf-debug-info)already stripped (build_id present, no debug sections); splitting would produce a useless companionhas external_debug_info but no local companion with matching build_idRunning the command twice is safe. It detects prepared pairs and skips them rather than overwriting a good companion with an empty one, and a module stamped on the first run keeps the same
build_idon later runs.Options
--dry-run--no-upload--require-dwarf--out-dir <DIR>--strip-names--build-id <UUID>build_idinstead of a random one--include-sources--wait/--wait-for <SECS>--ignore/--ignore-file--json--require-dwarfis the CI guard. Use it when every module in a build is expected to ship debug info, and you want the pipeline to fail rather than deploy something unsymbolicatable.--dry-runwrites nothing at all, including thebuild_id, so it is safe to run against a tree you do not want modified.Relationship to
wasm-splitThe split follows the same algorithm as
wasm-splitand uses the samewasmbinlibrary, so the companions it writes are equivalent to whatwasm-split -d <companion> --stripproduces: a sharedbuild_id, a companion retaining the Code section, and DWARF removed from the deployable. Stamping behavior matches too — both tools give every module they touch abuild_id.It is a reimplementation rather than a shared codepath.
wasm-splitis a binary-only crate in Symbolicator with no library target, so there is no API to depend on. Because the two are maintained separately, treat equivalence as a property this command aims for, not a guarantee inherited from upstream.prepareis also narrower thanwasm-splitby design. It is a pipeline for Sentry uploads, not a general-purpose WASM tool.Limitations
A module without DWARF gets a
build_idbut no debug file. Stamping makes the module identifiable; it does not make it symbolicatable. Nothing is uploaded for it, so stack traces resolve only as far as the runtime itself manages. For a name/symtab-only module that is usually function names and no line numbers, since browsers read thenamesection straight out of the deployed module. Uploading that module as its own debug file would not improve on what the runtime already reports, so the command does not. To get line-level frames, rebuild with DWARF and runprepareagain — thebuild_idfrom the earlier run is preserved.external_debug_infoalways points at the companion filename. There is no equivalent ofwasm-split --external-dwarf-url, so you cannot embed an absolute URL for browser DevTools to fetch. This does not affect Sentry, which resolves debug files bybuild_id.Splitting always strips. There is no mode that writes a companion while leaving the deployable module unstripped.
--out-dirredirects the companion only. The deployable module is always stripped in place so the path you deploy is the one that carries thebuild_id.--strip-namesapplies only to modules that are split. A skipped module keeps itsnamesection, which is what lets the runtime still report function names for it.Compatibility
The command reads module sections only, so it works regardless of which compiler produced the module. Build with DWARF enabled (Emscripten
-g, wasm-packdwarf-debug-info).Node API
The npm wrapper exposes the same flow: