Skip to content

cmux NEXT: hide the app executable's exports, keep names in an in-app symbol map (-11 MiB, cx-b6r7) - #20762

Merged
teamleaderleo merged 7 commits into
feat-cmux-nextfrom
op-app-size/hide-exports
Oct 15, 2026
Merged

teamleaderleo merged 7 commits into
feat-cmux-nextfrom
op-app-size/hide-exports

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

cmux NEXT's main executable exports all 110,746 of its symbols. The export trie (2.9 MiB), symbol table (1.8 MiB) and string table (6.8 MiB) take about 11.5 MiB of the installed app, and nothing looks them up at runtime. An audit found:

  • no -bundle_loader plug-ins or test hosts in the app;
  • dlsym calls only into system libraries, CEF and the shim;
  • Rust FFI linked statically;
  • Objective-C and Swift runtime lookups that go through metadata sections, not exports.

Leo approved hiding them, on one condition: nobody reads raw offsets.

  • Link: the cmux-next Release executable links with -Xlinker -no_exported_symbols. Debug (DEV) is unchanged. The Linux builder mirrors the flag from the pbxproj (cmuxterm-hq#2100).
  • Landing order: the nightly picks up the flag only after hq#2100 lands and nightly-next's builder copy is redeployed (nightly-next-linux/deploy.sh). Until then nothing changes: the exports stay, and the map is written alongside them.
  • Symbol map: before the release strip removes the names, scripts/strip-release-bundle.sh runs scripts/write-app-symbol-map.py. It writes Contents/Resources/cmux-symbols.bin: for each slice, the LC_UUID and the __TEXT,__text functions as offsets, compressed with raw DEFLATE. A second strip pass over the stripped executable keeps the map.
  • In-app symbolication: StackSymbolicator names frames in the app executable through the map (AppSymbolMap, only the running slice is loaded), and frames in other images through dladdr as before.
    • Hang records (debug.hangs) and uncaught exception traces (now read from callStackReturnAddresses, which feed the next launch's crash notice and issue report) both use it.
    • A frame with no name keeps its offset from the image header, for symbolication from the dSYM. The dSYM is still built from the unstripped link and uploaded as before.
arm64 cmux executable nightly (exports kept) this PR, Release after strip
export trie 2.88 MiB 8 B
symbol table 1.76 MiB 0.07 MiB (4,856 entries)
string table 6.82 MiB 0.19 MiB
names total 11.46 MiB 0.27 MiB
cmux-symbols.bin (arm64 slice) none 2.6 MiB

Net: about −8.6 MiB installed for the arm64 nightly. The universal Release executable from this head strips to 189,379,088 bytes (arm64 slice 91,812,368), and its universal map is 5.1 MiB (447,385 functions over two slices).

Sample trace. Below is a sample of the stripped Release app from this head, on a capture Mac. sample printed ??? (in cmux) for every app frame. The same offsets, resolved through the shipped map by AppSymbolMap, give these frames, and the per-function offsets match atos against the dSYM:

main + 16
CmuxNextApp.main() + 1884
thunk for @escaping @callee_guaranteed () -> () + 28
partial apply for closure #2 in LineTransport.start(onEvent:onClose:) + 36
LineTransport.readLoop(fd:onEvent:onClose:) + 216
partial apply for closure #5 in MainThreadWatchdog.start() + 24
MainThreadWatchdog.watch(sampler:) + 488
ProviderConnection.readLoop() + 292

Ghostty's internal (Zig) functions have no symbols even today, so they still resolve to the nearest Ghostty export (for example ghostty_surface_new + 255408), the same as before this change. Their image offset is kept for dSYM lookup.

Verification

  • Red, CI: at 1c49b8b (test only), the checks job failed in Script tests (job 116145395398): cmux-symbols.bin missing.
  • Green: at 635d7bd, the same checks job ran Script tests green (job 116175372658), including the second-strip-pass case (red locally before its fix). Crash safety is green as well. Its remaining reds are base reds fixed by Fix base red: chief_brains state root asks the build identity (cx-8oudl) #20767.
  • Release compile (fleet): check-release-compile.sh passed on the merged head 9b10dd2 (fleet step 0c4e8ad3b304, xcodebuild exit 0).
  • Release app build at the exact head: a Release app built and stripped from 3735e93 (the same link settings and map writer) gave the sizes and trace above. The commits since then change the decoder's checked reads, the second-pass fix, and the base merge.

Review

A correctness review subagent covered this PR and cmuxterm-hq#2100.

  • Fixed: the nightly's second strip pass rewrote the map with the one name that survives the first strip (__mh_execute_header). The map now stays when it already covers the executable's UUIDs. The test failed before the fix and passes after it.
  • Follow-ups, none blocking:
    • a Swift test that decodes a map the script wrote;
    • returning nil past the end of __text, so stubs are not named after the last function;
    • looking up address - 1 for frames after the first;
    • decoding names lazily, so the first exception trace does not inflate the whole map.

Changelog

Changed: cmux NEXT takes about 11 MB less disk space (the app executable no longer carries its symbol names; hang and crash traces still name app functions).

🤖 Generated with Claude Code

cmux NEXT's executable will link with -no_exported_symbols, and the
release strip then removes every name from it. Expect the strip to write
Contents/Resources/cmux-symbols.bin first (per slice: LC_UUID and the
__TEXT,__text functions as offsets), and a second strip pass over the
stripped executable to keep it. Runs in the cmux-next checks job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 4ca98eb2-0a7f-46dd-b7ae-b2603c0112ed

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…b6r7)

Link the cmux-next Release executable with -no_exported_symbols: nothing
resolves its symbols at runtime, and its 110,746 exports cost about
11.5 MiB (export trie, symbol and string tables) once the release strip
can drop them.

So nobody reads raw offsets, the release strip first writes
Contents/Resources/cmux-symbols.bin (scripts/write-app-symbol-map.py): per
slice, the LC_UUID and the __TEXT,__text functions as offsets, raw DEFLATE.
StackSymbolicator names app-executable frames through it (AppSymbolMap)
and other images through dladdr; hang records and uncaught exception
traces (now from callStackReturnAddresses) both use it. A frame with no
name keeps its image offset for the dSYM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

all web/size checks green

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Not verified: macOS jobs did not run: compile and app tests were skipped on 9b10dd2ed8; ci-status is green without them.

CI passes on 9b10dd2ed8 (run 38662185733 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

…rt (cx-b6r7)

Xcode refuses -no_exported_symbols in OTHER_LDFLAGS and wants its own
setting, LD_EXPORT_SYMBOLS = NO (cmux-next Release). AppSymbolMap's
public init takes Data and UUID, so it needs public import Foundation.
Both from the exact-head fleet Release build (exit 65).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 14, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

teamleaderleo and others added 4 commits October 14, 2026 17:07
The crash ratchet counted AppSymbolMap's byte subscripts and integer
conversions (26 index_subscript, 8 int_conversion in CmuxNextControl).
Read the file through a small cursor that returns nil past the end, with
checked subscripts and truncatingIfNeeded conversions: a damaged map
gives no names instead of a trap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (cx-b6r7)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…b6r7)

The nightly strips twice. After the first pass, __mh_execute_header is
still in __TEXT,__text, so the second pass rewrote the map with that one
name under the same UUID. Leave the map alone when it already has every
slice UUID of the executable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo merged commit 628debc into feat-cmux-next Oct 15, 2026
102 of 103 checks passed
@teamleaderleo
teamleaderleo deleted the op-app-size/hide-exports branch October 15, 2026 06:48
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
github-actions Bot added a commit that referenced this pull request Oct 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

cmux-next tour of 9b10dd2ed80ca44825d74e6e019f4e43b86b3818 (run)

Path-selected interactions

Interaction Changed paths
core Packages/macOS/CmuxNext/Sources/CmuxNextApp/Crash/UncaughtExceptionRecorder.swift
Packages/macOS/CmuxNext/Sources/CmuxNextControl/Diagnostics/AppSymbolMap.swift
Packages/macOS/CmuxNext/Sources/CmuxNextControl/Diagnostics/HangLog.swift
Packages/macOS/CmuxNext/Sources/CmuxNextControl/Diagnostics/StackSymbolicator.swift
Packages/macOS/CmuxNext/Sources/CmuxNextControl/Diagnostics/ThreadStackSampler.swift

Core UI (before): 12 ok

tour

Video (mp4)

# Step Result Command
1 Launch ok
2 New workspace ok cmux workspace new --name Tour
3 Type in the terminal ok cmux workspace name:Tour screen current pane current tab current terminal current write --text "echo hello from the cmux-next tour\r"
4 Split right ok cmux action run splitRight
5 Split down ok cmux action run splitDown
6 New terminal tab ok cmux action run newSurface
7 Browser tab ok cmux tab new-browser --url https://example.com
8 Hide the sidebar ok cmux action run toggleSidebar
9 Show the sidebar ok cmux action run toggleSidebar
10 Notification ok cmux notify --title Tour --body "Hello from CI"
11 Notifications panel ok cmux action run showNotifications
12 Agent chat ok cmux agent new-chat
Screenshots (11)

1. Launch
Launch

2. New workspace
New workspace

3. Type in the terminal
Type in the terminal

4. Split right
Split right

5. Split down
Split down

6. New terminal tab
New terminal tab

7. Browser tab
Browser tab

8. Hide the sidebar
Hide the sidebar

10. Notification
Notification

11. Notifications panel
Notifications panel

12. Agent chat
Agent chat

### Core UI \(after\): 12 ok

tour

Video (mp4)

# Step Result Command
1 Launch ok
2 New workspace ok cmux workspace new --name Tour
3 Type in the terminal ok cmux workspace name:Tour screen current pane current tab current terminal current write --text "echo hello from the cmux-next tour\r"
4 Split right ok cmux action run splitRight
5 Split down ok cmux action run splitDown
6 New terminal tab ok cmux action run newSurface
7 Browser tab ok cmux tab new-browser --url https://example.com
8 Hide the sidebar ok cmux action run toggleSidebar
9 Show the sidebar ok cmux action run toggleSidebar
10 Notification ok cmux notify --title Tour --body "Hello from CI"
11 Notifications panel ok cmux action run showNotifications
12 Agent chat ok cmux agent new-chat
Screenshots (11)

1. Launch
Launch

2. New workspace
New workspace

3. Type in the terminal
Type in the terminal

4. Split right
Split right

5. Split down
Split down

6. New terminal tab
New terminal tab

7. Browser tab
Browser tab

8. Hide the sidebar
Hide the sidebar

10. Notification
Notification

11. Notifications panel
Notifications panel

12. Agent chat
Agent chat

Tours are data in scripts/cmux-next/media/tours: add a step or a tour file to show your feature.

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