Skip to content

feat: external sourcemap upload for compiled binaries#518

Merged
BYK merged 4 commits intomainfrom
feat/external-sourcemaps
Mar 20, 2026
Merged

feat: external sourcemap upload for compiled binaries#518
BYK merged 4 commits intomainfrom
feat/external-sourcemaps

Conversation

@BYK
Copy link
Member

@BYK BYK commented Mar 20, 2026

Problem

Compiled Bun binaries produce minified stack traces with names like BJ8, pp1, kQ8 — making Sentry issue grouping inaccurate:

  • False splits: CLI-1D, CLI-BW, CLI-98 are the same SeerError but split into 3 issues (237 users) because different binary versions produce different minified names
  • False merges: CLI-N groups 84 users worth of different errors (Internal Error, You do not have permission) into one issue because they share the same minified function name
  • Lost context: Stack traces show func(bin) instead of handleResolvedTargets(issue/list.ts)

Solution: Two-Step Build with External Sourcemap Upload

Step 1: Bun.build()     →  bin.js + bin.js.map    (bundle TS, no compile)
        sentry-cli       →  inject debug IDs       (into JS + map)
        sentry-cli       →  upload to Sentry       (with /$bunfs/root/ prefix)
Step 2: Bun.build()     →  sentry-linux-x64       (compile JS, no sourcemap)

The sourcemap is uploaded to Sentry for server-side resolution — never embedded in or shipped with the binary.

Verified End-to-End ✅

Triggered a test error with the built binary. Sentry now shows fully resolved stack traces:

Before (minified):

at G8 (/$bunfs/root/bin.js:23947:19) [in-app]

After (resolved with source context):

at throwApiError (../src/lib/api/infrastructure.ts:48:9)

        43 |   const status = response?.status ?? 0;
        44 |   const detail =
        45 |     error && typeof error === "object" && "detail" in error
        46 |       ? stringifyUnknown((error as { detail: unknown }).detail)
        47 |       : stringifyUnknown(error);
  >     48 |   throw new ApiError(
        49 |     `${context}: ${status} ${response?.statusText ?? "Unknown"}`,

at unwrapResult (../src/lib/api/infrastructure.ts:88:5)

Measured Impact (linux-x64)

╔══════════════════════════════╤══════════════╤════════════════════╗
║ Metric                       │ No Sourcemap │ External Upload    ║
║                              │ (current)    │ (this PR)          ║
╠══════════════════════════════╪══════════════╪════════════════════╣
║ Gzipped download             │   29.32 MB   │    29.36 MB        ║
║ Δ vs current                 │     —       │   +0.04 MB (+0.1%) ║
╟──────────────────────────────┼──────────────┼────────────────────╢
║ bsdiff+zstd (V1→V2)         │   17.43 KB   │    18.26 KB        ║
║ Δ vs current                 │     —       │   +0.83 KB         ║
╟──────────────────────────────┼──────────────┼────────────────────╢
║ Raw binary                   │  101.81 MB   │   102.34 MB        ║
║ Δ vs current                 │     —       │   +0.54 MB (+0.5%) ║
╟──────────────────────────────┼──────────────┼────────────────────╢
║ SM file (Sentry only)        │     —       │    7.93 MB         ║
╚══════════════════════════════╧══════════════╧════════════════════╝

For comparison, inline sourcemaps would cost +2.30 MB gzipped and +37 KB per delta — ~60× worse on download size.

Key Implementation Details

  • sentry-cli sourcemaps inject adds debug IDs to the JS file before compile. This provides belt-and-suspenders matching: debug ID (primary) + filename with /$bunfs/root/ prefix (fallback)
  • --url-prefix '/$bunfs/root/' matches the virtual filesystem path Bun uses in compiled binary stack traces
  • minify: false in Step 2 because the JS is already minified in Step 1 — avoids double-minification producing different output
  • Upload is non-fatal: local builds and PR checks without SENTRY_AUTH_TOKEN still work, just skip upload

Changes

script/build.ts

  • Step 1: Bun.build() with sourcemap: "external" and minify: truebin.js + bin.js.map
  • Sourcemap upload: sentry-cli sourcemaps inject + upload with --url-prefix (non-fatal)
  • Step 2: Bun.build() with compile and minify: false → native binary
  • Intermediate files cleaned up after compile

.github/workflows/ci.yml

  • Pass SENTRY_AUTH_TOKEN to the build-binary job

@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Telemetry

  • Include user email in Sentry telemetry context by BYK in #513
  • Track TTY vs non-TTY invocations via metric by betegon in #482

Other

  • (help) Fuzzy "Did you mean?" suggestions for command typos by BYK in #516
  • (upgrade) Add progress spinners for version check and download phases by BYK in #515
  • External sourcemap upload for compiled binaries by BYK in #518
  • Dynamic cache-backed shell completions with fuzzy matching by BYK in #465

Bug Fixes 🐛

  • (completions) Populate project cache from listProjects by betegon in #517
  • (help) Hide ASCII banner when stdout is not a TTY by betegon in #501
  • (json) Flatten view command JSON output for --fields filtering by BYK in #495
  • (polling) Throw TimeoutError instead of bare Error on timeout by BYK in #503
  • (project) Fallback to org listing when bare slug matches an organization by betegon in #475
  • (setup) Auto-configure zsh fpath for shell completions by betegon in #509
  • Isolate multiregion 403 tests from env-var auth tokens by BYK in #514
  • Only mention token scopes in 403 errors for env-var tokens by BYK in #512
  • Suggest similar projects on project-search miss (CLI-A4) by BYK in #511
  • Preserve ApiError type in Seer handler + suggest trial start command (CLI-N, CLI-1D/BW/98) by BYK in #510
  • Add 403 scope guidance to issue list error handling (CLI-97) by BYK in #508
  • Propagate 403 from multi-region fan-out instead of returning empty list (CLI-89) by BYK in #507
  • Lowercase project slug in URL-parsed issue short IDs (CLI-C8 follow-up) by BYK in #506
  • Handle EIO stream errors gracefully in bin.ts by BYK in #505
  • Use fuzzyMatch for similar project suggestions and add tests (CLI-C0) by BYK in #504
  • Use resolved org in numeric issue ID 404 hint (CLI-BT) by BYK in #502
  • Include API endpoint in error messages for better diagnostics (CLI-BS) by BYK in #500
  • Enrich 403 on org listing with token scope guidance (CLI-89) by BYK in #498
  • Add 400 suggestions to org-all issue list path (CLI-BY) by BYK in #497
  • Lowercase project slug in issue arg parsing (CLI-C8) by BYK in #496
  • Enrich short ID 404 with org context and suggestions (CLI-A1) by BYK in #494
  • Suggest similar projects when project not found in org (CLI-C0) by BYK in #493
  • Event 404 hint should suggest different project, not repeat failing command by BYK in #492
  • Enrich event 404 errors with retention and format suggestions (CLI-6F) by BYK in #491
  • Add actionable suggestions for 400 Bad Request on issue list (CLI-BM, CLI-7B) by BYK in #489
  • Detect issue short IDs passed to issue list (CLI-C3) by BYK in #488
  • Add Glob.match() polyfill + improve auto-detect diagnostics (CLI-7T) by BYK in #487
  • Add org-slug pre-check to dispatchOrgScopedList (CLI-9A) by BYK in #485

Internal Changes 🔧

  • (issue) Skip getProject round-trip in project-search resolution by betegon in #473
  • (resolve) Carry project data through resolution to eliminate redundant getProject calls by BYK in #486
  • (telemetry) Convert is_tty metric to span tag by betegon in #499
  • HTTP latency optimizations — diagnostics, cache warming, concurrency limits by BYK in #490
  • Switch from @sentry/bun to @sentry/node-core/light (~170ms startup savings) by BYK in #474
  • Regenerate skill files by github-actions[bot] in b7b240ec

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

Codecov Results 📊

126 passed | Total: 126 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests
Passed Tests
Failed Tests
Skipped Tests

✨ No test changes detected

All tests are passing successfully.

✅ Patch coverage is 100.00%. Project has 1047 uncovered lines.
✅ Project coverage is 95.79%. Comparing base (base) to head (head).

Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    95.79%    95.79%        —%
==========================================
  Files          180       180         —
  Lines        24897     24897         —
  Branches         0         0         —
==========================================
+ Hits         23850     23850         —
- Misses        1047      1047         —
- Partials         0         0         —

Generated by Codecov Action

Two-step build: bundle TS → JS+sourcemap, then compile JS → binary.
The sourcemap is uploaded to Sentry for server-side stack trace
resolution, never shipped to users in the binary.

Verified end-to-end: stack traces in Sentry now show original function
names (throwApiError, unwrapResult) and file paths
(src/lib/api/infrastructure.ts:48) instead of minified names (G8, W41)
and //root/bin.js.

Impact vs current (no sourcemaps):
  Download size: +0.04 MB gzipped (+0.1%)
  Delta patches:  +0.83 KB per release
  Binary size:   +0.54 MB raw (+0.5%)

Build changes:
- script/build.ts: Step 1 bundles with external sourcemap,
  sentry-cli injects debug IDs + uploads with //root/ prefix,
  Step 2 compiles the JS without re-minifying
- CI: passes SENTRY_AUTH_TOKEN to build-binary job for upload
- Intermediate JS/map files cleaned up after compile
@BYK BYK force-pushed the feat/external-sourcemaps branch from 558d898 to 62d8b8e Compare March 20, 2026 14:15
Address Seer review: single quotes are not recognized as string
delimiters by Windows cmd.exe, causing malformed arguments.
Copy link

@cursor cursor bot left a comment

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.

Address Bugbot: double-quoted "//root/" gets expanded by POSIX
shells (Seer said single quotes break Windows). Bypass the shell
entirely with execFileSync + argument array — no quoting needed.
CI only runs on Linux, so single quotes safely prevent $bunfs shell
expansion without Windows compat concerns. Removes the execFileSync
complexity.
@BYK BYK merged commit 1b9bb95 into main Mar 20, 2026
22 checks passed
@BYK BYK deleted the feat/external-sourcemaps branch March 20, 2026 18:20
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.

1 participant