Skip to content

Fixes #26046 - #26600

Merged
cmux-bot[bot] merged 15 commits into
feat-cmux-nextfrom
26046-workspace-view-retention
Nov 1, 2026
Merged

cmux-bot[bot] merged 15 commits into
feat-cmux-nextfrom
26046-workspace-view-retention

Conversation

@austinywang

@austinywang austinywang commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #26046

A window in cmux NEXT kept every workspace it had ever shown mounted: the controller, layout tree, panes and strips stayed alive until critical memory pressure. Only the GPU surfaces had a cap (the terminal warm-set LRU). In the issue's measured run, visiting 400 workspaces took the app's footprint from 310 MiB to 868 MiB. It never came back down, because nothing below critical pressure released the parked set.

The original freeze and SSH failure were not reproduced (see the issue comments). This fixes the largest per-workspace cost that was measured.

Change

  • ParkedWorkspaceBudget (CmuxNextBridge) holds the cap and the eviction rule. A window keeps its 32 most recently shown workspaces parked. Parking one more tears down the least recently parked one, using the same teardown() the critical-pressure path uses. An evicted workspace mounts again the next time it is shown.
  • These never leave: the workspace under a top page, multi-view companions, and the workspace parked in that same call. The workspace about to show (park(_:leaving:)) neither counts toward the budget nor leaves. show() keeps its original order (park the current one, then take the target out), so the current controller of a rebuilt same-id model is still torn down.
  • 32 is four times the old cap of 8, which was removed in cx-8w027.5 because cycling through 9 workspaces made every switch a cold mount.
  • Docs: plans/cmux-next/tab-lifecycle.md and the WarmSetBudget doc comment.

Trade-off: when you return to an evicted workspace, it mounts cold. Terminals reattach through a daemon replay (the same path as a surface the LRU evicted), and strip and screen-bar scroll reset. This is what critical pressure already did. Page tabs stay, because closeGoneLocalTabs only closes panes the live tree no longer lists.

Regression test

ParkedWorkspaceBudgetTests:

  • Pure eviction-policy tests.
  • A real WindowController visits 40 one-terminal workspaces and must end with exactly the 32 most recent parked. With the set full, the oldest parked workspace and then the previous one must swap back in warm (same controller). An evicted one must mount again, and the count stays at 32.

Command (fleet): cmux-ci run --class light --script scripts/ci/package-test-lane.sh --ref <sha> --env CMUX_CI_XCODE_APP=/Applications/Xcode_26.6.app --arg=suite --arg=Packages/macOS/CmuxNext --arg=ParkedWorkspaceBudgetTests,ParkedWorkspaceApplyTests,SidebarSameTurnTests

  • Red at a24bb02 (test plus the unwired policy type only): step 276efa027c19a944b7940cbf, result in a follow-up comment. By trace, parked.count is 39, not 32.
  • Head fecdab6: step 723b94610cf8d6c7ae63219b. All 3 suites passed: ParkedWorkspaceBudgetTests (3 tests), ParkedWorkspaceApplyTests, SidebarSameTurnTests.

Observed / Not run

  • Observed: the head-step results above. Earlier, at 298afd7, the same suites passed and SidebarMultiViewWindowTests was skipped because the host has no display. Prepush gate. Four review rounds, the last one SHIP at fecdab6 (findings and fixes in the PR comment). Tagged build 26046-fix at fecdab6, fleet job 0909f10d4b952feae3af1c5f, published.
  • Not run: the issue's 400-workspace visitation harness before and after (the memory numbers above come from the issue's baseline, not from a new run). SidebarMultiViewWindowTests (needs a display). No GUI dogfood of the build yet.

Changelog

  • Fixed: cmux NEXT no longer keeps every workspace you have visited loaded, so memory stays bounded after visiting hundreds of workspaces.

No capture: no visible UI change; this only changes memory retention.

🤖 Generated with Claude Code

austinywang and others added 3 commits October 27, 2026 20:02
…arked (red, #26046)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d, not every one (#26046)

Visiting 400 workspaces kept 400 controllers, layout trees, panes and strips
mounted until critical memory pressure (footprint 310 to 868 MiB in the
issue's measured run). Past ParkedWorkspaceBudget's capacity the least
recently parked workspace leaves, as under critical pressure, and mounts
again when shown. The one under a top page and multi-view companions never
leave, and the workspace being shown leaves the parked set before the
current one parks, so it is never trimmed. 32 is well above the old budget
of 8, which made cycling 9 workspaces cold (cx-8w027.5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iew, #26046)

parkContentForPage parks the shown workspace while content is still set, so
the top-page keep rule could not name it; it survived only by being newest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@austinywang austinywang added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 28, 2026
… the target through the trim (review, #26046)

Taking the target out of parked first left a current controller of the same
id (a rebuilt workspace model) parked beside its replacement. The original
order is back; park(_:keeping:) keeps the workspace about to show. The test
now shows the oldest parked workspace while the set is full.

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

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review c5c7fb4: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr26600 -f ref=26046-workspace-view-retention -f platform=macos -f backend_mode=local

red: cmux-next checks (god files, concurrency, crash safety, l10n); NEW: red check: No fleet host names in gallery fixtures is not red on the base dfc2b58; do not merge until green; see lane-rules

Shipped in:

…ed budget (review, #26046)

show() parks the current one while the target is still parked, then takes the
target out. Counting the target made a switch to a parked workspace with a
full set evict one more and leave 31.

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

Copy link
Copy Markdown
Contributor Author

Review (correctness-first subagent, three rounds), with resolutions:

  1. Round 1 (on 001e825). No blocking issue. Low: during parkContentForPage, the workspace a page covers survived the trim only because it was the newest. Fixed in 298afd7: the workspace parked in the same call is always kept.

  2. Round 2 (on 298afd7). FIX:

    • Taking the target out of parked before parking the current controller could leave a stale controller with the same id (from a rebuilt model) parked next to its replacement.
    • The "oldest swaps back warm" check ran when the set was not full, so it could never fail.

    Fixed in b9ada93: the original park-then-remove order is back, and the test now runs that check while the set is full.

  3. Round 3 (on b9ada93). FIX: the trim counted the target, which was about to leave the set, so switching to a parked workspace with a full set evicted one extra and left 31. Fixed in fecdab6: the target no longer counts toward the budget.

  4. Final round (on fecdab6, full diff). Traced every test expectation at fecdab6 and confirmed the integration test fails at the red commit a24bb02 (39 parked instead of 32). Checked every park(_:) caller (show, parkContentForPage, multi-view companion) and the rebuilt same-id model case. Review (subagent): SHIP at fecdab6

Fleet runs of the focused suites at the red and head SHAs are in progress; I'll post the results when they finish.

@austinywang austinywang added the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Oct 28, 2026
@austinywang

Copy link
Copy Markdown
Contributor Author

Evidence at head fecdab6:

@austinywang

Copy link
Copy Markdown
Contributor Author

Red confirmed at a24bb02 (the test plus the unwired policy type only). Step 276efa027c19a944b7940cbf, run https://github.com/manaflow-ai/cmuxterm-hq/actions/runs/40626047730:

✘ aWindowVisitingManyWorkspacesKeepsABoundedSetMounted() ParkedWorkspaceBudgetTests.swift:63: (controller.parked.count → 39) == (ParkedWorkspaceBudget.standard.capacity → 32)
✘ ...:66: Set(controller.parked.keys) holds k0…k38 instead of the 32 most recent
✘ ...:67: (controller.parked["k0"] → WorkspaceContentController) == nil
✘ ...:84: (controller.parked.count → 39) == 32
ParkedWorkspaceBudgetTests  failed (exit 1)   ParkedWorkspaceApplyTests  passed

The two pure policy tests pass at a24bb02, because the type exists there; only the window test fails, since nothing is wired yet. At head fecdab6 the same command passes all 3 suites (step 723b94610cf8d6c7ae63219b).

Keeps both: workspace hibernation's releaseParked(_:) and the parked budget.
The hibernation design's memory table notes the 32-per-window cap.

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

cursor Bot commented Oct 28, 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.

@blacksmith-sh

blacksmith-sh Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Found 7 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp07d0rdub/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmp1ba5sx9_/summary-fail.json (live):/summary-fail.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
test_runtime_added_app_capabilities_are_runtime_only (main.StaticCapabilityRuntimeO
nlyTests.test_runtime_added_app_capabilities_are_runtime_only)/
test_runtime_added_app_capabilities_are_runtime_only (main.StaticCapabilityRuntimeO
nlyTests.test_runtime_added_app_capabilities_are_runtime_only)
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@austinywang austinywang removed the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Oct 28, 2026
@austinywang

Copy link
Copy Markdown
Contributor Author

Head c1ad099 merges feat-cmux-next at 96367c7 (conflict in WindowController+Parking.swift; I kept both workspace hibernation's releaseParked(_:) and this PR's budget). The Swift diff over the merged base is identical to the reviewed fecdab6 diff. git diff HEAD^2 HEAD -- Packages compares equal line for line.

The merge commit also has:

  • one updated line in plans/cmux-next/workspace-hibernation-DESIGN.md (its memory table now names the 32-per-window cap);
  • two tests/test-execution.toml entries that the pre-commit hook registered (tests/test_ci_runs_on_fallback.py and tests/test_workflow_file_size.py, both linux-guard). Both files come from the base, not this PR.

Every red at c1ad099 is a base red:

Check Cause This PR?
cmux-next swift test (rerun attempt 2) CloudActionDescriptionTests.everyCloudActionHasADescription: cloudOpenDesktop has no description No. Base red from #23952, fixed by #26777
cmux-next generated files (rerun attempt 2) The regenerated patch changes action-surfaces.json (space move-to-start), actions.md counts, and schemas/defaults/cmux-next.json tunable hashes for CloudGitHubHandlers.swift and TerminalLinkOpenControl.swift No. None of these files or sources are in this PR, and each file is unchanged from the base parent
cmux-next crash-class rules objc-selectors probe on DividerHandleView.swift super.flagsChanged No. This PR adds no super call
nightly-guards / Guard status, release-notary Release and nightly workflow guard tests No. Also red on the base parent 96367c7

Attempt 1 of the two package jobs never ran: the runner hook refused them with "a fleet build is waiting for the host".

The focused suites passed at fecdab6 (step 723b94610cf8d6c7ae63219b), and the red run at a24bb02 failed as expected. The same step at c1ad099 (670ab5a7a11f619e1e2c4480) is still in the fleet queue. Tagged build 26046-fix at c1ad099: job 4a25b4ef22937fe8fb25b705, published.

@austinywang austinywang added the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Oct 28, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator

For awareness: a recent fleet suite step for this PR reported success while SidebarMultiViewWindowTests skipped every test (no display on the runner), so that suite never actually ran. cmux#26870 now marks a fully skipped suite as "no result: required tests did not run". A rerun on a runner with a display (or the capture lane) would give this PR a real result for that suite.

Docked workspaces (window.dockedWorkspaces) widened what counts as on screen
beside the shown workspace to WindowState.isOnScreenBeside. The parked budget
keeps the same set now: keepsParked uses isOnScreenBeside, so the trim never
unloads a visible docked workspace or the stage's workspace, and
releaseParked() keeps the base's exact predicate.

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

cursor Bot commented Nov 1, 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.

@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 00:35 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 00:35 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 00:40 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 02:57 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 02:57 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 02:57 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 10:08 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 10:08 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 10:09 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 11:42 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 11:42 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 11:52 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 11:52 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 13:09 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 13:09 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 13:09 Inactive
@cursor

cursor Bot commented Nov 1, 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.

@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 17:06 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 17:06 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 17:07 Inactive
@cmux-bot
cmux-bot Bot merged commit c3fa01c into feat-cmux-next Nov 1, 2026
149 of 153 checks passed
@cmux-bot
cmux-bot Bot deleted the 26046-workspace-view-retention branch November 1, 2026 18:10
lawrencecchen added a commit that referenced this pull request Nov 1, 2026
…o CmuxNextHostedChief (#31218)

The arch ratchet's app_lines row was +15 at the feat-cmux-next tip against the
last green arch tip 006666a (#26600 +22, #31055 +16, #22953 +15, #31147 +5,
#31106 -1, #31182 -42). CmuxNextApp may not grow.

ServerReachPlan (which paired servers the app reaches, and the parsers of
chief.list, team.hosts.list, `cmux link show` and `cmux link peer list`) holds
no app state: it is value types and pure functions over CloudChief
(CmuxNextHostedChief) and ServerReach (CmuxNextRemote). It moves as it is into
CmuxNextHostedChief with a public API; ServerReachService in the App calls it.
CmuxNextHostedChief now depends on CmuxNextRemote (service -> service).

Behavior-neutral: the only body change is parseChiefs, which read the chiefs
through the App's CloudChiefs.parseList after its guard proved `chiefs` is an
array; it now maps that array with CloudChief.parse itself, the same result.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — c5c7fb40 Deployed Nov 1, 2026 by cmux-bot[bot] via wait for same-key tree publisher #96958
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants