Skip to content

perf: strip debug symbols from Node binaries (-17 MiB)#1019

Merged
BYK merged 1 commit into
mainfrom
chore/node24-optimizations
May 23, 2026
Merged

perf: strip debug symbols from Node binaries (-17 MiB)#1019
BYK merged 1 commit into
mainfrom
chore/node24-optimizations

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented May 23, 2026

Summary

Strip debug symbols and unneeded sections from cached Node binaries before fossilize's SEA injection. Node.js ships with full symbol tables (~17 MiB on linux-x64) that aren't needed at runtime.

Why before injection?

Stripping must happen before SEA injection because postject corrupts the ELF section-to-segment layout, making GNU strip fail on the injected binary. The build pipeline is now:

fossilize cache download → strip → SEA injection → binpunch → gzip

Results (linux-x64)

Metric Before After Savings
Raw binary 125 MiB 108 MiB -17 MiB (-14%)
Compressed 34 MiB 30 MiB -4 MiB (-12%)
Startup unchanged unchanged

Comparison vs Bun (v0.34.0)

Metric Bun (v0.34.0) Node SEA (this PR)
Download (gzip) 32 MiB 30 MiB
Raw binary 106 MiB 108 MiB
Startup ~1.0s ~1.0s (on par)
Completion ~0.18s ~0.15s

Platform handling

  • Linux: strip --strip-unneeded — removes symbols + debug sections
  • macOS: strip -x — removes local symbols (requires re-codesigning, handled by fossilize)
  • Windows: skipped — PE binaries don't ship debug symbols

Copy link
Copy Markdown
Contributor

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

Reviewed by Cursor Bugbot for commit c35452e. Configure here.

Comment thread script/build.ts
// strip removes debug symbols and unneeded sections (~17 MiB on linux-x64).
// Must run BEFORE fossilize's SEA injection — postject corrupts ELF section
// layout, making strip fail on the injected binary.
await stripCachedNodeBinaries(platforms);
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.

Strip runs before cache exists

Medium Severity

stripCachedNodeBinaries runs immediately before fossilize, but fossilize fills .node-cache during that invocation. When the cache is missing (typical CI fresh checkouts), stripping is skipped and the SEA build uses an unstripped Node base, so the advertised ~17 MiB savings may not apply to release artifacts.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c35452e. Configure here.

Comment thread script/build.ts
// strip removes debug symbols and unneeded sections (~17 MiB on linux-x64).
// Must run BEFORE fossilize's SEA injection — postject corrupts ELF section
// layout, making strip fail on the injected binary.
await stripCachedNodeBinaries(platforms);
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.

Bug: The stripCachedNodeBinaries function is called before fossilize populates the .node-cache/ directory, causing the stripping step to be skipped in CI and fresh builds.
Severity: MEDIUM

Suggested Fix

Modify the build script to ensure fossilize downloads the Node binaries before stripCachedNodeBinaries is called. This could involve running a pre-download step or restructuring the compileAllTargets function to execute stripping after the cache is populated. Alternatively, cache the .node-cache directory in the CI workflow.

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: script/build.ts#L369

Potential issue: The function `stripCachedNodeBinaries` is invoked at
`script/build.ts:369` before the `fossilize` process runs. On a clean environment like a
CI runner, the `.node-cache/` directory, which `fossilize` is responsible for creating
and populating, does not yet exist. The `stripCachedNodeBinaries` function checks for
this directory's existence and exits early if it's not found. As a result, the Node
binaries are downloaded and used for SEA injection by `fossilize` without ever being
stripped of their symbols. This means the intended 17 MiB size reduction for release
artifacts is not achieved in any CI-produced build.

Did we get this right? 👍 / 👎 to inform future reviews.

Strip cached Node binaries in fossilize's .node-cache/ before SEA
injection. Node.js ships with full symbol tables (~17 MiB on linux-x64)
that aren't needed at runtime.

Stripping must happen BEFORE fossilize's postject injection because
postject corrupts the ELF section-to-segment layout, making GNU strip
fail on the injected binary.

Results (linux-x64):
  Raw binary:   125 MiB → 108 MiB (-14%)
  Compressed:   34 MiB → 30 MiB (-12%)
  Startup:      unchanged (strip removes metadata, not code)
@BYK BYK force-pushed the chore/node24-optimizations branch from c35452e to 572973e Compare May 23, 2026 22:47
@github-actions
Copy link
Copy Markdown
Contributor

Codecov Results 📊

✅ Patch coverage is 100.00%. Project has 4283 uncovered lines.


Generated by Codecov Action

@BYK BYK merged commit 98da7ad into main May 23, 2026
28 checks passed
@BYK BYK deleted the chore/node24-optimizations branch May 23, 2026 22:56
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