Skip to content

feat(wasm-debug-files): Add prepare command for WASM debug setup - #3403

Closed
d2anamaria wants to merge 5 commits into
masterfrom
ana/feat/wasm/debug-file-pipeline
Closed

feat(wasm-debug-files): Add prepare command for WASM debug setup#3403
d2anamaria wants to merge 5 commits into
masterfrom
ana/feat/wasm/debug-file-pipeline

Conversation

@d2anamaria

Copy link
Copy Markdown

Problem

WebAssembly needs manual work before Sentry can symbolicate crashes. Today users run wasm-split to inject a build_id and split DWARF into a companion file, then run sentry-cli debug-files upload --type wasm on 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.

sentry-cli debug-files prepare ./dist

It scans for .wasm modules, classifies each one, splits the ones that have DWARF, and uploads the companions.

Behavior

Every module the command inspects gets a build_id if it has none, whether or not it can be split. The build_id is 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 .wasm module with embedded DWARF:

  1. Inject a build_id if the module has none.
  2. Write a *.debug.wasm companion that keeps the Code section and DWARF.
  3. Strip .debug_* sections from the deployable .wasm, in place.
  4. Point the stripped module at its companion via external_debug_info.
  5. Upload the companion to Sentry.

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

Outcome When
Split Module had DWARF; companion written
Already prepared Companion exists with matching build_id
Would split --dry-run preview
Skipped Module is not splittable; stamped, warning printed

Warnings

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:

  • Name/symtab only: no line-level symbolication (name/symtab only)
  • No debug sections: no debug information; rebuild with DWARF (Emscripten -g, wasm-pack dwarf-debug-info)
  • Already stripped: already stripped (build_id present, no debug sections); splitting would produce a useless companion
  • Dangling reference: has external_debug_info but no local companion with matching build_id

Running 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_id on later runs.

Options

Flag Effect
--dry-run Classify and preview; write nothing, upload nothing
--no-upload Split only
--require-dwarf Exit with an error if any scanned module lacks DWARF
--out-dir <DIR> Write companions to a directory; the deployable is always stripped in place
--strip-names Also drop the name section from the deployable; the companion keeps it
--build-id <UUID> Use an explicit build_id instead of a random one
--include-sources Upload source bundles alongside companions
--wait / --wait-for <SECS> Wait for server-side processing
--ignore / --ignore-file Skip paths while scanning
--json Machine-readable output

--require-dwarf is 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-run writes nothing at all, including the build_id, so it is safe to run against a tree you do not want modified.

Relationship to wasm-split

The split follows the same algorithm as wasm-split and uses the same wasmbin library, so the companions it writes are equivalent to what wasm-split -d <companion> --strip produces: a shared build_id, a companion retaining the Code section, and DWARF removed from the deployable. Stamping behavior matches too — both tools give every module they touch a build_id.

It is a reimplementation rather than a shared codepath. wasm-split is 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.

prepare is also narrower than wasm-split by design. It is a pipeline for Sentry uploads, not a general-purpose WASM tool.

Limitations

A module without DWARF gets a build_id but 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 the name section 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 run prepare again — the build_id from the earlier run is preserved.

external_debug_info always points at the companion filename. There is no equivalent of wasm-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 by build_id.

Splitting always strips. There is no mode that writes a companion while leaving the deployable module unstripped.

--out-dir redirects the companion only. The deployable module is always stripped in place so the path you deploy is the one that carries the build_id.

--strip-names applies only to modules that are split. A skipped module keeps its name section, 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-pack dwarf-debug-info).

Node API

The npm wrapper exposes the same flow:

await cli.debugFiles.prepare({
  path: './dist',
  includeSources: true,
  wait: true,
});

…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
@d2anamaria
d2anamaria requested review from a team and szokeasaurusrex as code owners September 9, 2026 12:54
@d2anamaria
d2anamaria marked this pull request as draft September 9, 2026 12:54
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor
Fails
🚫 Please consider adding a changelog entry for the next release.
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

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 #skip-changelog to the PR description or adding a skip-changelog label.

Generated by 🚫 dangerJS against 9cd2f9d

Comment on lines +231 to +233
let dwarf_missing = results
.iter()
.any(|result| result.action == PrepareAction::Skipped && !result.quality.has_dwarf());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 2 potential issues.

Fix All in Cursor

❌ 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());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

--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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9cd2f9d. Configure here.

Comment thread src/utils/wasm.rs
Some(dir) => dir.join(name),
None => wasm_path.with_file_name(name),
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

--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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9cd2f9d. Configure here.

@szokeasaurusrex

Copy link
Copy Markdown
Member

Hey @d2anamaria, just wondering why you are opening this PR in this repo?

This is the repo for the legacy sentry-cli, which is no longer under active development. This feature should probably be getting implemented in the new sentry CLI's repo, instead.

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 🙏

@d2anamaria d2anamaria closed this Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants