feat(wasm-debug-files): Add prepare command for WASM debug setup - #1572
feat(wasm-debug-files): Add prepare command for WASM debug setup#1572d2anamaria wants to merge 7 commits into
Conversation
- Read and write WASM modules without dropping unknown sections - Classify debug quality: dwarf, symtab, none, or external companion - Split DWARF like wasm-split: companion keeps Code, deploy file loses .debug_* - Inject build_id when missing; detect already-prepared pairs - Stamp skipped modules with build_id when they have none - Add unit and property tests
- Scan for .wasm files, split DWARF modules, upload companions to Sentry - Flags: dry-run, no-upload, require-dwarf, out-dir, strip-names, build-id - Skip symtab and no-debug modules with warnings; do not fail the whole run - Human output: one block per file, yellow warnings, full paths - Add command tests
- Add prepare examples and notes to debug-files docs - Update agent skill reference for debug-files prepare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
| // Write the companion first: if the process dies between the two writes, an | ||
| // orphan companion is recoverable, whereas a stripped module whose DWARF was | ||
| // never saved anywhere is not. | ||
| await writeFile(expectedCompanion, split.companion); |
There was a problem hiding this comment.
Bug: The prepareWasmFile function does not create the output directory specified by --out-dir before attempting to write files, which will cause a crash if the directory does not exist.
Severity: HIGH
Suggested Fix
Before calling writeFile, ensure the parent directory exists by adding a call like await mkdir(dirname(expectedCompanion), { recursive: true }). This pattern is already used in other parts of the codebase like bundle-sources.ts.
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/wasm/prepare.ts#L590
Potential issue: The `prepareWasmFile` function attempts to write a companion file using
`writeFile` to a path derived from the `--out-dir` option. However, Node.js's
`fs/promises.writeFile` does not create parent directories by default. If a user
specifies an output directory that does not yet exist, the operation will fail with an
`ENOENT` error. This is a common use case for a CLI tool that is expected to create its
own output directories.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 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 8c8468e. Configure here.
| const name = basename(wasmPath).replace(WASM_EXTENSION, ""); | ||
| const fileName = `${name}${COMPANION_SUFFIX}`; | ||
| return join(outDir ?? dirname(wasmPath), fileName); | ||
| } |
There was a problem hiding this comment.
--out-dir overwrites same-named companions
High Severity
--out-dir names every companion from the module basename alone, so a recursive scan that hits two app.wasm files writes the same app.debug.wasm twice. The first module is stripped in place, then its companion is overwritten, so that DWARF is gone from both the deployable and disk and later runs cannot recover it.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8c8468e. Configure here.
| // Write the companion first: if the process dies between the two writes, an | ||
| // orphan companion is recoverable, whereas a stripped module whose DWARF was | ||
| // never saved anywhere is not. | ||
| await writeFile(expectedCompanion, split.companion); |
There was a problem hiding this comment.
Missing --out-dir directory creation
Medium Severity
--out-dir writes companions with writeFile and never creates the directory. writeFile does not create parents, so the documented prepare ./dist --out-dir ./symbols throws ENOENT unless ./symbols already exists. The error is uncaught and aborts the run after any earlier in-place stamps.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8c8468e. Configure here.
| } | ||
|
|
||
| return { | ||
| hint: `Uploaded ${uploads.length} debug companion(s) to ${params.org}/${params.project}`, |
There was a problem hiding this comment.
Source bundles counted as companions
Low Severity
filesUploaded is uploads.length minus failures, and --include-sources appends source-bundle DIFs into that same list. Human output then reports those extras as companions, so one companion plus one source bundle is shown as Uploaded 2 companions.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8c8468e. Configure here.
- Fail --require-dwarf when skipped modules have unreachable external_debug_info - Clarify hasDwarfQuality as build classification, not companion reachability - Add recommendation on symtab and none skips to verify DWARF build flags - Render skipped modules with a red header and cyan Recommendation line - Export lacksDwarf and cover dangling-pointer gate behavior in tests
|
A deploy |
Sounds like we should, yes. Also a simple warning in this case sounds like the wrong action as we are essentially uploading broken stuff? |
- Treat all `none` quality skips as missing DWARF, not already stripped - Recommend verifying DWARF build flags for every `none` skip, with or without build_id - Already-prepared modules return before skipWarning; this only affects skipped files - Remove misleading already-stripped message for nosym/sourcemap-only modules
- Repair a module carrying external_debug_info but no build_id instead of stamping a random id that can never match its companion - Adopt the companion's build_id, or stamp one shared UUID into both files - Report the pair as already-prepared so the companion gets uploaded - Write the companion first so a crash stays repairable on the next run - Keep the existing skip when the companion is remote, missing, or has no DWARF


Problem
Preparing WebAssembly for Sentry takes two tools today. You run
wasm-splitto inject abuild_idand pull DWARF out into a companion file, then runsentry debug-files upload --type wasmon that companion. Nothing tells you when a module was built without usable debug info, so the mistake surfaces later as an unsymbolicated stack trace.Solution
sentry debug-files prepare <path>...does both steps in one command.It scans the given files and directories for
.wasmmodules, splits the ones carrying inline DWARF, and uploads the companions. Org and project are auto-detected from DSN, env vars, or config defaults.--dry-runand--no-uploadneed no credentials.What it does per module
For a module with inline DWARF:
build_idif it has none.*.debug.wasmcompanion keeping every section, including Code and DWARF..debug_*sections from the deployable module, in place.external_debug_info.The deployable keeps its original path, so your build artifact does not move. Both files carry the same
build_id, which is how Sentry matches a stack frame to its debug file. The companion keeps the Code section because DWARF addresses are relative to it.A module that cannot be split is still stamped with a
build_id, then reported with a warning. Stamping matters: without abuild_ida module can never be symbolicated, not even from a debug file uploaded later. Nothing is uploaded for it.build_idalready exists--dry-runpreviewWarnings
A module without usable debug info does not fail the run unless
--require-dwarfis set. The warning says why it was skipped: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_idRe-running is safe. An already-prepared pair is detected and left alone rather than overwritten with an empty companion, and a module keeps the
build_idit was stamped with on the first run.Options
--dry-run--no-upload--require-dwarf--out-dir <DIR>--strip-namesnamesection from split deployables; the companion keeps it--build-id <UUID>--include-sources--wait/--wait-for <SECS>--ignore <GLOB>/--ignore-file <FILE>--require-dwarfis the CI guard, and it runs before anything is uploaded, so a build missing debug info fails without pushing files first. A module pointing at an external companion counts as having DWARF, so a dangling pointer does not fail the build.--ignoreglobs are relative to the tree you point at:--ignore 'vendor/**'withprepare ./distmeans./dist/vendor.Scanning rules
Directories are walked recursively.
*.debug.wasmfiles are skipped, since they are outputs of an earlier run. Naming a non-.wasmfile directly is an error; a directory with no modules is just an empty scan.Automation
--jsonreports the outcome per module, so a build script can act on the classification instead of grepping logs:{ "org": "my-org", "project": "my-project", "uploaded": true, "filesUploaded": 1, "modules": [ { "path": "dist/app.wasm", "action": "split", "quality": "dwarf", "buildId": "…", "companion": "dist/app.debug.wasm" } ] }The command exits non-zero when
--require-dwarffails, and when a companion fails server-side processing under--wait.Relationship to wasm-split
The split follows the same algorithm as Symbolicator's
wasm-split, and custom sections follow the WebAssembly tool conventions, so companions are meant to be interchangeable in Sentry. It is a reimplementation, not a shared codepath:wasm-splitis a binary-only Rust crate, so this ships its own minimal module reader. Only the module envelope is parsed and section payloads are re-emitted byte for byte, so sections the reader does not know about survive untouched.Limitations
build_idbut no debug file. Rebuild with DWARF and re-run to get line-level frames; thebuild_idis preserved.external_debug_inforecords the companion filename only. There is no equivalent ofwasm-split --external-dwarf-url. Sentry resolves bybuild_id, so this does not affect symbolication.--out-dir, re-running without the same--out-dirwill not find the companion and reports a dangling reference.