Skip to content

feat(debug-files): Add wasm-upload command for WASM debug setup - #1628

Open
sentry-junior[bot] wants to merge 8 commits into
mainfrom
port/cli-1572-wasm-upload
Open

sentry-junior[bot] wants to merge 8 commits into
mainfrom
port/cli-1572-wasm-upload

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Ported from getsentry/cli#1572 by @d2anamaria, which is being closed as part of the getsentry/cli archive. Toolkit already had the low-level pieces (lib/wasm/binary.ts, build-id.ts, split.ts, sentry wasm-split). Nothing on main or in another open PR covers this command.

Problem

Getting WebAssembly ready for Sentry takes two tools today. You run wasm-split to inject a build_id and move DWARF into a companion file. Then you run sentry debug-files upload --type wasm on that companion. If a module was built without usable debug info, nothing tells you. You find out later from an unsymbolicated stack trace.

Before / after

Before (v3, and main today):

sentry wasm-split dist/app.wasm -d dist/app.debug.wasm --strip
sentry debug-files upload --type wasm dist/app.debug.wasm

You name the companion yourself, run this once per module, and get no warning when a module has no DWARF.

After:

sentry debug-files wasm-upload dist/

One command for every module under dist/. On the Emscripten fixture used in the end-to-end check below (emcc -g):

Before After
dist/app.wasm 87,151 bytes, inline .debug_* sections 7,631 bytes, no .debug_*, one build_id, external_debug_info pointing at the companion. Same path, so the deploy doesn't change.
Companion none, or whatever name you gave -d dist/app.<build_id>.debug.wasm (87,151 bytes), uploaded
Module without DWARF uploads nothing, silently gets a build_id and a warning. --require-dwarf fails the run.
Second run wasm-split again would strip an already-stripped module every module reports already-prepared, no bytes change

Solution

sentry debug-files wasm-upload <path>... does both steps. It scans the paths for .wasm modules. For each module that has inline DWARF, it:

  1. Injects a build_id if the module doesn't have one.
  2. Writes a <stem>.<build_id>.debug.wasm companion. The companion keeps the Code section, because DWARF addresses are relative to it.
  3. Strips the .debug_* sections from the deployable module in place, so the artifact keeps its path.
  4. Points the module at the companion through external_debug_info, replacing any pointer a previous tool left.
  5. Uploads the companion.

A module that can't be split still gets a build_id and a warning. --require-dwarf turns that warning into a failure for CI. If a module has a companion but no build_id, the command repairs the pair instead of only warning about it. Running the command again on the same files is safe. --dry-run and --no-upload work without credentials. The org, project, and auth are resolved before any module is modified, so a missing one fails before anything is stripped.

The command also moves some duplicated code into shared helpers, as asked in review on the original PR:

  • --wait / --wait-for parsing moves to commands/debug-files/wait.ts
  • readSourceFile moves to commands/debug-files/read-file.ts
  • buildIgnoreMatcher moves to lib/scan/ignore.ts

upload, bundle-sources, and the sourcemap commands now import these helpers instead of keeping their own copies.

Which *.debug.wasm files are companions

A companion is a full copy of its module, so its contents can't tell it apart from a module with DWARF. Splitting a companion by mistake strips the DWARF its module points at. A scanned *.debug.wasm is treated as a companion only when:

  • it has the <stem>.<build_id>.debug.wasm name this command writes, or
  • another scanned module's external_debug_info points at it, or
  • it is <stem>.debug.wasm next to a <stem>.wasm with the same build_id. This is what wasm-split -d app.debug.wasm leaves, including with a remote --external-dwarf-url.

A <stem>.debug.wasm whose sibling has a different build_id is a separate debug build, so it's prepared and uploaded like any module. One with no sibling can't be told apart, so it's skipped with a warning that names the file.

build_id convention

The build_id section body is a varuint32 length prefix followed by the id, and the CLI reads the id after the prefix. A body of exactly 16 bytes with no prefix (the legacy form some producers write) is read as a raw UUID, the way symbolic reads it. It is not stamped again: a second build_id would make symbolic (last section wins) and @sentry/wasm (first section wins) disagree.

The readers on the other side now agree with this: symbolic#1093 (14.0.0-alpha.4), getsentry/sentry#128493 (stores the correct id plus the legacy shifted one), and @sentry/wasm 11.11.0 (sentry-javascript#24857). One gap remains on the SDK side: @sentry/wasm 11.12.0 only strips the prefix for 16-byte ids, so a wasm-ld --build-id=sha1 module (20-byte id) still produces events with the shifted id. That needs a fix in @sentry/wasm, not here.

Review notes carried over from cli#1572

Merged with main

main moved by about 390 commits. The merge adapts wasm-upload to the conventions that landed there:

  • every flag and positional uses a completion-tagged parser
  • examples live in command metadata
  • usage hints use the sentry`…` tag
  • Markdown is escaped with the @sentry/toolkit-core/markdown builders

debug-files upload drops its ValidationError import now that wait parsing lives in wait.ts.

sentry-cli v3 compatibility

v3 documents WASM as wasm-split app.wasm -d app.debug.wasm --strip followed by sentry-cli debug-files upload. That flow still works unchanged in v4 (sentry wasm-split takes the same flags). wasm-upload is a new v4 shortcut and changes no existing command. Running wasm-upload over a directory that the v3 flow already processed leaves the app.debug.wasm companion alone.

Validation

End-to-end with real Emscripten builds (emsdk, -g) of a small C crash, one per build_id flavour. debug-files wasm-upload ran against a mock Sentry, then each stripped module was crashed in headless Chromium with @sentry/wasm 11.12.0. "Uploaded companion" compares the id symbolic reads from the uploaded file with the stripped module's id.

build_id Uploaded companion matches module @sentry/wasm 11.12.0 event matches build_id sections .debug_* left in module
--build-id=uuid ✅ ✅ 1 0
--build-id=fast ✅ ✅ 1 0
--build-id=sha1 ✅ ❌ SDK sends the shifted id 1 0
none (CLI stamps one) ✅ ✅ 1 0
legacy bare 16 bytes ✅ ✅ 1 0

A second run reported already-prepared for every module and left them byte-identical. Naming cases, also end to end: a sentry wasm-split -d app.debug.wasm --external-dwarf-url https://… output keeps its companion untouched, a separate app.debug.wasm debug build is split and uploaded, and a lone app.debug.wasm is skipped with a warning. This check doesn't cover server-side symbolication of a frame through the uploaded companion.

Checks:

  • tsc --noEmit for packages/cli
  • vitest across test/lib/wasm, test/commands/debug-files, test/commands/sourcemap, test/lib/sourcemap, test/lib/scan
  • full CLI suite after the merge: the only failures are sandbox-only cases (EACCES/EPERM/readonly as root, plus telemetry/delta e2e), none in debug-files, wasm, or sourcemap
  • ci:policy, oxlint, and oxfmt --check for packages/cli
  • check:fragments, check:docs-sections, check:errors
  • generate:docs leaves no diff

Refs getsentry/cli#1572

via BYK, @betegon.

--

View Session [Sentry]

Preparing WebAssembly for Sentry took two tools: `wasm-split` to inject a
`build_id` and pull DWARF into a companion, then `debug-files upload` on
that companion. Nothing flagged modules built without usable debug info,
so the mistake only surfaced as an unsymbolicated stack trace.

`sentry debug-files wasm-upload <path>...` does both steps. It scans for
`.wasm` modules, and for each one with inline DWARF it injects a
`build_id` if missing, writes a `<stem>.<build_id>.debug.wasm` companion
(keeping the Code section, since DWARF addresses are relative to it),
strips `.debug_*` from the deployable in place, points it at the
companion via `external_debug_info`, and uploads the companion. Modules
that can't be split are still stamped and reported with a warning;
`--require-dwarf` turns that into a CI failure.

`--wait`/`--wait-for` parsing, source-file reading, and the ignore
matcher move into shared helpers so `upload`, `bundle-sources`, and the
sourcemap commands reuse them instead of carrying copies.

Ported from getsentry/cli#1572, which is being closed with the repo
archive.

Co-Authored-By: BYK <burak.kaya@sentry.io>
@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Nov 12, 2026 3:48pm UTC

Request Review

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

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/lib/wasm/prepare.ts Resolved
`--out-dir` was passed directly to `writeFile`. If the directory didn't
exist yet, including the documented `./symbols` example, the command
threw ENOENT before writing any companion. The command now creates the
companion's directory first.

Co-Authored-By: BYK <burak.kaya@sentry.io>
Comment thread packages/cli/src/lib/wasm/prepare.ts Resolved
`wasm-upload` stripped modules in place and wrote companions before
resolving the org, project, and chunk-upload options. A missing target or
an auth failure then aborted after the files had changed, leaving
stripped modules whose companions were never uploaded. When an upload
will run, the target is now resolved before any module is touched, the
same way `debug-files upload` does it.

A module whose `external_debug_info` companion was missing also got a
random `build_id` stamped on the skip path. If the companion showed up
later, that random id could never match it, so the pair could no longer
be repaired. These modules are now left unstamped.
Adapt wasm-upload to conventions that landed on main: completion-tagged
parsers for every flag and positional, examples in command metadata, the
sentry`...` usage-hint tag, and the toolkit-core Markdown builders.

Co-Authored-By: BYK <burak.kaya@sentry.io>
sentry-junior Bot added a commit that referenced this pull request Nov 12, 2026
Bring in main through the rebased #1628 and fit wasm pairs into what landed
there:

- `sourcemap upload <glob>` now narrows wasm pairs the way it narrows JS
  pairs: a pair is kept when the module or its map matches, and the
  zero-pairs diagnostic counts only matching files so the `.wasm.map`
  subtraction lines up with the JS tally.
- `--dry-run` now reports what a real run would change. `mapWritten` and
  `moduleStamped` were always false in a dry run, so `sourcemap inject
  --dry-run` counted every wasm pair as skipped. `setSourcemapDebugId`
  returns `changed` instead of `written` to say the same in both modes.
- `relativePosix` is shared with JS discovery instead of duplicated.

Co-Authored-By: bete <28983162+betegon@users.noreply.github.com>
Co-Authored-By: BYK <burak.kaya@sentry.io>

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

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/lib/wasm/prepare.ts Resolved
A module that still had inline DWARF and an `external_debug_info` pointer
left by another tool kept the old pointer next to the new one after
`wasm-upload` split it. `inspectWasm` reads the last pointer and the
companion lookup reads the first, so the two could follow different
files. The split now drops any existing pointer before appending the one
for the new companion. `splitWasm` itself is unchanged, so `wasm-split`
keeps parity with the Rust tool.
Producers that predate the tool-conventions length prefix write the id
as a bare 16-byte body. The decoder rejected it, so callers treated the
module as unstamped and appended a second, random build_id. symbolic
reads the last section and @sentry/wasm the first, so events stopped
matching both maps and debug files for a module that matched before.

Read a 16-byte body as is, mirroring getsentry/symbolic#1093.

Co-Authored-By: bete <28983162+betegon@users.noreply.github.com>
wasm-upload skipped every *.debug.wasm, so a real debug build named
app.debug.wasm was never uploaded. Companions are now recognized by the
name this command writes (<stem>.<build_id>.debug.wasm), by another
module's external_debug_info, or by sharing a build id with a sibling
<stem>.wasm (wasm-split -d with a remote --external-dwarf-url). A
*.debug.wasm with a differently identified sibling is prepared as a
module. One with no sibling is skipped with a warning, since splitting
a companion would strip the DWARF its module points at.

Co-Authored-By: bete <28983162+betegon@users.noreply.github.com>

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

Reviewed by Cursor Bugbot for commit 3f580b3. Configure here.

Dropping companions could leave nothing to prepare, but the command still
resolved the org, project, and auth before noticing. A normal run over a
directory of wasm-split companions then failed with a missing org/project
error. The empty check now runs after companions are dropped.

This branch was previously deployed

1 inactive deployment
Preview — a5a8ed1e Deployed Nov 12, 2026 by vercel[bot]
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