Stop shared agent-index reloads from re-rendering every pane
Fixes https://github.com/manaflow-ai/cmux/issues/22703.
Summary
SharedLiveAgentIndex reloads at most every 5 s while agent hooks write the hook store (~/.cmuxterm), and each full reload posts .sharedLiveAgentIndexDidChange without userInfo. Every Workspace answered with an unconditional objectWillChange.send(), so WorkspaceContentView re-ran and rebuilt every pane: one GhosttyTerminalView.updateNSView and portal reconciliation per terminal, while the user was idle. On a window with many panes that work, and the SwiftUI graph update it drives, is the main-thread time in the no-app-frame hang reports.
The publish was added (v0.64.18) so Bonsplit's tab bar re-read Fork Conversation availability. The tab context menu now reads availability through its provider and refresh handler when it opens, and restored-agent reconciliation mutates the @Observable RestoredAgentLifecycleCoordinator, so no view needs the workspace publish. The observer keeps reconciling restored agents and no longer publishes.
Evidence
From Sentry (400 full events of CMUXTERM-MACOS-3ZVC, 3,000 light events, 14-day rates by release):
- 3ZVC, 3PAX, 1PTM and AZ are one bucket: App Hang events with no in-app frame, grouped by the entry culprit, which changed in 0.65.1. Devices hitting it in 14 days: 0.64.25 17.4%, 0.65.0 12.6%, 0.65.1 10.6%, 0.65.2 6.9%. It is not a 0.65 regression; it predates 0.65.
- About a quarter of the events block in system IPC (LaunchServices
_applicationNameChanged, SkyLight display state) and are outside cmux. The rest are SwiftUI graph updates, layout and display-list work. - 82 of 146 multi-pane hang events show, in the 60 s before capture, a burst with one
geometryPublicationper surface within 200 ms: every pane'supdateNSViewran. Bursts recur every few seconds at idle, then breadcrumbs stop and socket main-hop timeouts follow.
Ruled out: the issue's @AppStorage/UserDefaults lead. In a standalone check on macOS 26.6, @AppStorage views did not re-evaluate for unrelated-key or same-value writes, and 20k–40k observers cost milliseconds per write.
Scope
This PR stops the idle reload bursts. https://github.com/manaflow-ai/cmux/pull/22901 owns the other half of https://github.com/manaflow-ai/cmux/issues/22703: the Bonsplit split root leaked one withObservationTracking registration per render, so each burst re-rendered the tree many times (https://github.com/manaflow-ai/bonsplit/pull/291). No files overlap. Defaults chosen here: keep this PR to the cmux-side publish rather than folding in the Bonsplit bump, because the Bonsplit pointer has its own merge order in the integration PR.
Testing
WorkspaceContentViewVisibilityTests/testSharedAgentIndexReloadDoesNotReevaluatePaneContent sets up three workspaces in a controlled project root and awaits each root's discovery. It then hosts the main window, waits for it to go quiet, and posts the full-reload .sharedLiveAgentIndexDidChange. It expects no workspace publish and no WorkspaceContentView or ContentView body evaluation. The observer now delivers on the main queue and handles the change before post returns (SharedLiveAgentIndex is @MainActor). The test therefore asserts at a causal point instead of draining for queued tasks, and production code has no test seam. WorkspaceProcessWideObserverRetirementTests checks that retirement removes the observer.
Regression proof: run 40394452100 executed one test with the unconditional publication restored and failed with workspacePublishes = 4 and workspaceContentBody = 1. The current test preserves those assertions, posts the real notification, waits for each workspace's project-root discovery, and explicitly displays the hosted window. It lives in an extension of the same serialized suite; it is wired into cmuxTests.
The previous verified main catch-up is 79671bf968545675f1788d8a8fc9326341df1eef. Its app, test and project diff is byte-identical to reviewed and fleet-built d319d3666397a91b7c850150c04b237eb405fb21; the catch-up only includes seven inherited CI/tooling commits. Focused run 40734289457 requests both WorkspaceContentViewVisibilityTests and WorkspaceProcessWideObserverRetirementTests on that code. It passed: 25 tests executed in two suites (23 visibility tests and 2 observer-retirement tests), including the agent-index reload regression in 0.571 seconds. No assertions were skipped or relaxed.
Latest base update: 6eb01e6d1ff55fce4bf773901107b12e0cca44df merges origin/main at aa4dcd453ce9a2ce5baa66c0863ef8d5758a4350 without rewriting history. The only conflict was cmux.xcodeproj/project.pbxproj; scripts/merge-main.sh --tip --no-guards --remote origin resolved it by combining added entries and normalizing the project. The fix and regression source files did not change. Current-head CI is pending.
Observed / Not run
- Observed on the latest merge: project normalization, 13 app-source wiring-tool tests, app-source membership, test-source membership (1,319 files), and
git diff --checkpass. The local localization-defaults, full test-wiring recipe, and feature-flag checks hit the existing 60-second limit on the overloaded Mac; they are not recorded as passes. No local Swift or Xcode build was run. - Observed: the Sentry investigation recorded above; the historical red regression log; all six scoped static preflight checks (including 13 app-source wiring-tool tests, no skips); file-size budgets; clean merge-conflict checks; structured review with no actionable correctness findings (0.99 confidence).
- Observed: native run 40734289457 compiled the app/test targets and passed all 25 tests on macOS 15. Previous-head CI run 40735909143 is green on
79671bf968545675f1788d8a8fc9326341df1eef. Its changed-suite job executed 34 tests across five suites on macOS 26, including the reload regression; app-host compilation, warning-budget validation and CLI binary smoke checks also passed. The agent-chat guard retry passed 125 tests with zero failures after its first attempt stalled in dependency installation. - Observed: controller build
99f8205f6a6f094176d77692compiled and packagedd319d3666397a91b7c850150c04b237eb405fb21onaws-m4pro-3in 183 seconds, artifactsha256:4554140ac74d23a3851e390aba2578c608a3488ca5aa18687d519ac234995542. The tag isissue-22703-agent-index-reloads. HQ publication is blocked by the local certificate trust error; the verified build artifact exists. - Not run: no live capture of an idle window with many panes or release signing/notarization run. The native app build does not establish those results. No test assertion, timeout, retry policy, packaging, signing, or CLI behavior was weakened for this fix.
Changelog
Fixed: idle windows with many panes no longer hang when agent hooks refresh the shared agent index.
🤖 Generated with Claude Code