Skip to content

Fix stale group name in title bar after rename (#5404) - #5408

Merged
austinywang merged 6 commits into
mainfrom
issue-5404-group-rename-titlebar
Jun 5, 2026
Merged

austinywang merged 6 commits into
mainfrom
issue-5404-group-rename-titlebar

Conversation

@austinywang

@austinywang austinywang commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Closes #5404.

Bug

Renaming a workspace group updated the name in the sidebar header but the window title bar kept showing the old name (e.g. stayed Group 1).

Root cause — two sources of truth for the group's display name

At group creation, createWorkspaceGroup seeds both the anchor workspace's customTitle/title and WorkspaceGroup.name to the same string (TabManager.swift:5864-5883 → addWorkspace(title:) → setCustomTitle). The sidebar header reads group.name; the window chrome (custom title bar, NSWindow.title, toolbar command label) reads the selected anchor's tab.title. The anchor is represented exclusively by the group header in the sidebar (SidebarWorkspaceRenderItem.swift:51-53), so its own title is never otherwise shown.

renameWorkspaceGroup mutated only group.name. The @Published workspaceGroups change re-rendered the sidebar, but nothing updated the anchor's title and nothing refreshed the imperatively-cached title-bar text — so the two copies drifted.

Fix — one observed source

group.name becomes the single source of truth for an anchor's displayed name, via one derivation on the owner that holds both stores:

  • TabManager.resolvedWorkspaceDisplayTitle(for:) — returns the group's name when the workspace is its group's anchor, otherwise the workspace's own title.
  • The custom title bar (ContentView.updateTitlebarText), NSWindow.title (TabManager.windowTitle(for:)), and the toolbar command label (WindowToolbarController) all route through it.
  • renameWorkspaceGroup refreshes the NSWindow title inline and posts Notification.Name.workspaceGroupNameDidChange, which the two imperatively-cached chrome surfaces observe to re-derive.

This eliminates the whole class of "a window-chrome surface shows a grouped anchor's stale name", not just the title bar.

Tests

Two-commit red/green in cmuxTests/WorkspaceGroupTests.swift (already wired into the test target):

  • Commit 1 adds the behavioral test + the seam stubbed at current (buggy) behavior → red.
  • Commit 2 implements the derivation + routing → green.

renamingGroupUpdatesAnchorDisplayTitle asserts the anchor's resolved display title follows a rename; renamingGroupLeavesNonAnchorMemberTitleAlone guards the derivation against over-reaching to non-anchor members. The pure title-bar rendering is only reachable via XCUITest; the derivation seam is the cleanly testable behavioral boundary, so the unit tests target that.

Localization

No new user-facing strings — the displayed name flows from the already-localized Group %lld default (or user input); the new notification is internal. No Localizable.xcstrings changes required.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Low Risk
Localized UI title derivation and notification-driven refresh; no auth, data, or persistence changes.

Overview
Fixes #5404: after renaming a workspace group, the sidebar showed the new name but window chrome (custom title bar, NSWindow.title, toolbar “Cmd:” label) could still show the old anchor title.

Window chrome now uses TabManager.resolvedWorkspaceDisplayTitle(for:), which returns the group’s name when the selected tab is a group anchor, and the workspace’s own title otherwise. renameWorkspaceGroup updates the window title immediately and posts .workspaceGroupNameDidChange; ContentView and WindowToolbarController listen with the notification object set to their tabManager so only that window refreshes. Unit tests cover anchor rename and non-anchor members unchanged.

Reviewed by Cursor Bugbot for commit 01ca8d4. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes #5404: window title bar and toolbar label kept the old group name after rename. Anchors now use the group’s name across window chrome, scoped to only the posting window.

  • Bug Fixes
    • Added TabManager.resolvedWorkspaceDisplayTitle(for:) to return group.name for anchors; otherwise the workspace’s title.
    • Routed the custom title bar, NSWindow.title, and the toolbar command label through this resolver.
    • On rename, refresh NSWindow title and post .workspaceGroupNameDidChange; ContentView and WindowToolbarController observe it with object: tabManager to prevent cross-window refresh.

Written for commit 01ca8d4. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Window titles and focused-command labels now reflect workspace group names and update immediately when a group is renamed.
    • Notification handling scoped per window to prevent unrelated windows from refreshing titles on remote renames.
  • Tests

    • Added regression tests verifying workspace group rename behavior and that only anchor workspace titles mirror group names.

austinywang and others added 2 commits June 4, 2026 17:31
Renaming a workspace group updates the sidebar header (reads group.name)
but the window title bar keeps the old name, because the title bar reads
the anchor workspace's own (stale) title. Introduce the
resolvedWorkspaceDisplayTitle(for:) seam stubbed at the current buggy
behavior, plus a behavioral test that fails on rename. The fix follows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make group.name the single source of truth for a workspace group anchor's
displayed name across all window chrome. resolvedWorkspaceDisplayTitle(for:)
returns the group's name when the workspace is its group's anchor (which the
sidebar already represents exclusively by the group header), otherwise the
workspace's own title. The custom title bar, NSWindow.title, and the toolbar
command label all route through it, and renameWorkspaceGroup refreshes the
NSWindow title inline plus posts workspaceGroupNameDidChange so the two
imperatively-cached chrome surfaces re-derive. A rename now propagates to the
title bar consistently with the sidebar, eliminating the stale-name class.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 5, 2026 2:41am
cmux-staging Building Building Preview, Comment Jun 5, 2026 2:41am

@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

TabManager adds resolvedWorkspaceDisplayTitle(for:) and a workspaceGroupNameDidChange notification. renameWorkspaceGroup posts the notification and refreshes window title; ContentView and WindowToolbarController use the resolved title and observe scoped notifications. Tests cover anchor vs non-anchor rename behavior.

Changes

Workspace group display name propagation

Layer / File(s) Summary
Core display title resolution and notification
Sources/TabManager.swift
Adds resolvedWorkspaceDisplayTitle(for:), updates windowTitle(for:) to trim the resolved title, and defines Notification.Name.workspaceGroupNameDidChange.
Group rename with notification
Sources/TabManager.swift
renameWorkspaceGroup writes the trimmed group name, refreshes the selected tab's window title, and broadcasts workspaceGroupNameDidChange.
Title bar and toolbar refresh on group rename
Sources/ContentView.swift, Sources/WindowToolbarController.swift
ContentView and WindowToolbarController derive title text from tabManager.resolvedWorkspaceDisplayTitle(for:) (trimmed) and add notification observers scoped to their tabManager to refresh cached title chrome when a group is renamed.
Regression tests for group rename display
cmuxTests/WorkspaceGroupTests.swift
Adds tests that verify the anchor workspace's displayed title tracks the group's name, while non-anchor member workspace titles remain unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • Ari4ka

Poem

🐰 I hopped through tabs to find a name askew,
I nudged the manager, and the titlebar knew.
Notifications scoped, the chrome learned to sing,
Anchors wear the group-name like a tiny spring.
Tests clap their paws — now the rename is true.

🚥 Pre-merge checks | ✅ 17 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: resolving a stale group name in the title bar after rename, with the issue number reference.
Linked Issues check ✅ Passed All changes comprehensively address issue #5404: introduced resolvedWorkspaceDisplayTitle resolver, routed title bar/NSWindow.title/toolbar through it, added notification-driven refresh scoped by tabManager, and added regression tests.
Out of Scope Changes check ✅ Passed All changes are tightly scoped to the fix: UI title derivation, notification routing, and corresponding tests; no unrelated functionality or refactoring beyond what's required to resolve the stale title issue.
Cmux Swift Actor Isolation ✅ Passed Code changes respect Swift 6 actor isolation: TabManager and WindowToolbarController are @MainActor; new method safely reads MainActor properties; notification handlers properly isolated.
Cmux Swift Blocking Runtime ✅ Passed PR introduces only event-driven synchronization through notifications and direct synchronous updates with no blocking primitives (sleep, semaphores, locks, or timing waits) in production code.
Cmux No Hacky Sleeps ✅ Passed All PR changes are Swift files. Rule applies only to TypeScript/JavaScript/shell/non-Swift build scripts, explicitly excluding Swift.
Cmux Algorithmic Complexity ✅ Passed Linear scan of workspaceGroups via first(where:), not nested. Called via coalesced handlers for single selected tab only. Same pattern exists at line 3672. Workspace groups implicitly bounded.
Cmux Swift Concurrency ✅ Passed PR introduces no legacy async patterns. Uses NotificationCenter posting, SwiftUI onReceive, and simple methods. Task usage in WindowToolbarController is within allowed AppKit callback boundaries.
Cmux Swift @Concurrent ✅ Passed PR contains no async functions, only synchronous UI-bound work in @MainActor contexts; no @concurrent annotations needed or violated per swift-concurrent-annotation.md rules.
Cmux Swift File And Package Boundaries ✅ Passed PR complies with swift-file-package-boundaries.md: focused bug fix with <31 lines per file (below 250-line threshold), no responsibility mixing, and fits the allowed "focused bug fix" case.
Cmux Swift Logging ✅ Passed No logging violations: no print/NSLog/debugPrint added to app code, no ad hoc file logging, no MainActor Logger constants, no secret exposure.
Cmux User-Facing Error Privacy ✅ Passed No new user-facing error messages, alerts, or privacy-sensitive data exposed. Notification constant is internal ("cmux.workspaceGroupNameDidChange"). Changes are UI display logic only.
Cmux Full Internationalization ✅ Passed PR adds no new user-facing strings. Changes route existing user data through resolvedWorkspaceDisplayTitle(). Notification constant is internal protocol token, not UI text. Tests excluded from i18n.
Cmux Swiftui State Layout ✅ Passed No new @Published/@observable state; touches existing @State/@EnvironmentObject incidentally; state updates via explicit handlers with debouncing, not render-time; AppKit bridge correct.
Cmux Architecture Rethink ✅ Passed Fixes split ownership bug by routing all UI surfaces through resolvedWorkspaceDisplayTitle() resolver. Establishes group.name as single source of truth with clear invariants and comprehensive tests.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR modifies display logic of existing main workspace window UI without introducing new standalone cmux-owned windows that require close-shortcut registration.
Description check ✅ Passed PR description comprehensively covers all required template sections with detailed context, clear bug reproduction, root cause analysis, fix explanation, test strategy, and localization notes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-5404-group-rename-titlebar

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes #5404: after renaming a workspace group, window chrome (custom title bar, NSWindow.title, toolbar command label) kept showing the old name because they read tab.title on the anchor workspace, which was only seeded at creation time and never updated on rename. The fix adds TabManager.resolvedWorkspaceDisplayTitle(for:) as the single derivation point — returning group.name for anchors — and routes all three chrome surfaces through it.

  • resolvedWorkspaceDisplayTitle(for:) added to TabManager; windowTitle(for:) in TabManager, updateTitlebarText() in ContentView, and the toolbar label in WindowToolbarController all now call through it instead of reading tab.title directly.
  • renameWorkspaceGroup now calls updateWindowTitleForSelectedTab() inline and posts .workspaceGroupNameDidChange scoped to self, so ContentView and WindowToolbarController each observe only their own window's tabManager and refresh their cached text without cross-window side effects.
  • Two unit tests added to WorkspaceGroupTests.swift: one verifying the anchor's resolved title tracks the group rename, and one verifying non-anchor members are unaffected.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to deriving a display title and wiring notification observers, with no data mutations or auth paths affected.

The fix eliminates a dual-source-of-truth bug with a single derivation function. Notification scoping to the posting tabManager is correct. Mutation order in renameWorkspaceGroup (update array first, then refresh, then notify) is correct. The Task { @MainActor } observer pattern matches the established convention in WindowToolbarController. Unit tests cover both the fixed path and the non-anchor guard. No legacy concurrency anti-patterns or actor isolation issues introduced.

No files require special attention.

Important Files Changed

Filename Overview
Sources/TabManager.swift Adds resolvedWorkspaceDisplayTitle(for:) as the single source of truth for anchor display names; updates renameWorkspaceGroup to refresh NSWindow title inline and post the new notification; adds workspaceGroupNameDidChange notification name. Logic is correct and ordering of mutations → title update → notification is safe.
Sources/ContentView.swift Routes updateTitlebarText through resolvedWorkspaceDisplayTitle; adds a workspaceGroupNameDidChange observer scoped to this view's tabManager to prevent spurious cross-window refreshes. Pattern is consistent with other observers in the file.
Sources/WindowToolbarController.swift Toolbar command label now reads through resolvedWorkspaceDisplayTitle; new workspaceGroupNameDidChange observer scoped to tabManager (matching object: at post site), using the same queue: .main + Task { @MainActor } pattern as every other observer in this file.
cmuxTests/WorkspaceGroupTests.swift Two targeted regression tests added: renamingGroupUpdatesAnchorDisplayTitle verifies the anchor's resolved title follows a group rename; renamingGroupLeavesNonAnchorMemberTitleAlone verifies the derivation does not over-reach to regular member workspaces.

Sequence Diagram

sequenceDiagram
    participant User
    participant Sidebar
    participant TabManager
    participant ContentView
    participant WindowToolbarController
    participant NSWindow

    User->>Sidebar: Rename group
    Sidebar->>TabManager: renameWorkspaceGroup(groupId:name:)
    TabManager->>TabManager: "workspaceGroups[index].name = trimmed"
    TabManager->>TabManager: updateWindowTitleForSelectedTab()
    TabManager->>TabManager: resolvedWorkspaceDisplayTitle(for: anchor) → group.name
    TabManager->>NSWindow: "title = resolvedName"
    TabManager->>NotificationCenter: post(.workspaceGroupNameDidChange, object: self)
    NotificationCenter->>ContentView: (scoped to this tabManager)
    ContentView->>ContentView: scheduleTitlebarTextRefresh()
    ContentView->>TabManager: resolvedWorkspaceDisplayTitle(for: selectedTab)
    TabManager-->>ContentView: group.name
    ContentView->>ContentView: "titlebarText = group.name"
    NotificationCenter->>WindowToolbarController: (scoped to this tabManager)
    WindowToolbarController->>WindowToolbarController: scheduleFocusedCommandTextUpdate()
    WindowToolbarController->>TabManager: resolvedWorkspaceDisplayTitle(for: selectedTab)
    TabManager-->>WindowToolbarController: group.name
    WindowToolbarController->>WindowToolbarController: "toolbar label = Cmd: group.name"
Loading

Reviews (4): Last reviewed commit: "Merge branch 'main' of https://github.co..." | Re-trigger Greptile

Comment on lines +57 to +65
observers.append(center.addObserver(
forName: .workspaceGroupNameDidChange,
object: nil,
queue: .main
) { [weak self] _ in
Task { @MainActor [weak self] in
self?.scheduleFocusedCommandTextUpdate()
}
})

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.

P2 Notification scoped to all windows, not just the renaming window

The observer is registered with object: nil, so every WindowToolbarController in every open window refreshes its toolbar label whenever any window renames a group. Because WindowToolbarController.start(tabManager:) already captures the owning tabManager before installObservers() runs, the observer can be scoped to the specific TabManager instance that posts the notification, eliminating spurious cross-window refreshes. The same consideration applies to the parallel observer in ContentView, though there the tabManager reference would need to be passed through the closure capture.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — scoped both observers (WindowToolbarController and ContentView) to the notification's object (the posting TabManager, already passed as object: self from renameWorkspaceGroup), so a rename in one window no longer triggers spurious refreshes in others. Fixed in the latest commit.

— Claude Code

Address Greptile P2: the workspaceGroupNameDidChange observers used
object: nil, so in a multi-window setup every window's title bar and
toolbar refreshed on any window's rename. Scope both observers to the
notification's object (the posting TabManager), which the post already
identifies via object: self, eliminating spurious cross-window refreshes.

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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 89a8c91. Configure here.

Comment thread Sources/TabManager.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/TabManager.swift`:
- Around line 20-88: The WorkspaceGitMetadataProbeLimiter and related types
(WorkspaceGitMetadataProbeWaiter, cancelledWaiterIds logic) should be moved out
of TabManager into their own service boundary (e.g., WorkspaceGitProbeService or
WorkspaceGitMetadataProbeLimiter in a new file) and exposed via a protocol that
TabManager depends on; create a protocol (e.g., WorkspaceGitProbeLimiting) that
declares acquire()/release() async methods, implement it with the existing
WorkspaceGitMetadataProbeLimiter logic in a new source file, update TabManager
to accept the protocol as an injected dependency (initializer/property) and stop
declaring these types in Sources/TabManager.swift, and update callers/tests to
pass a concrete or fake implementation for isolated unit testing.
- Around line 80-86: cancelWaiter can leak UUIDs because you insert into
cancelledWaiterIds when the waiter isn’t in waiters, but later the waiter may be
removed/resumed elsewhere leaving that id forever; fix by removing the id from
cancelledWaiterIds whenever you actually remove/resume a waiter. Concretely, in
cancelWaiter and in the code paths that remove a waiter (the block that does
waiters.remove(at:) and calls waiter.continuation.resume(...)), call
cancelledWaiterIds.remove(id) after/resolution so the set cannot grow unbounded;
keep the existing insert branch for races but ensure every successful
removal/resume of a waiter calls cancelledWaiterIds.remove(id).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 60e581fa-e02f-48d3-b8fe-ee62a8d95bc8

📥 Commits

Reviewing files that changed from the base of the PR and between 3af30e9 and 01ca8d4.

📒 Files selected for processing (2)
  • Sources/ContentView.swift
  • Sources/TabManager.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/TabManager.swift`:
- Around line 20-88: The WorkspaceGitMetadataProbeLimiter and related types
(WorkspaceGitMetadataProbeWaiter, cancelledWaiterIds logic) should be moved out
of TabManager into their own service boundary (e.g., WorkspaceGitProbeService or
WorkspaceGitMetadataProbeLimiter in a new file) and exposed via a protocol that
TabManager depends on; create a protocol (e.g., WorkspaceGitProbeLimiting) that
declares acquire()/release() async methods, implement it with the existing
WorkspaceGitMetadataProbeLimiter logic in a new source file, update TabManager
to accept the protocol as an injected dependency (initializer/property) and stop
declaring these types in Sources/TabManager.swift, and update callers/tests to
pass a concrete or fake implementation for isolated unit testing.
- Around line 80-86: cancelWaiter can leak UUIDs because you insert into
cancelledWaiterIds when the waiter isn’t in waiters, but later the waiter may be
removed/resumed elsewhere leaving that id forever; fix by removing the id from
cancelledWaiterIds whenever you actually remove/resume a waiter. Concretely, in
cancelWaiter and in the code paths that remove a waiter (the block that does
waiters.remove(at:) and calls waiter.continuation.resume(...)), call
cancelledWaiterIds.remove(id) after/resolution so the set cannot grow unbounded;
keep the existing insert branch for races but ensure every successful
removal/resume of a waiter calls cancelledWaiterIds.remove(id).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 60e581fa-e02f-48d3-b8fe-ee62a8d95bc8

📥 Commits

Reviewing files that changed from the base of the PR and between 3af30e9 and 01ca8d4.

📒 Files selected for processing (2)
  • Sources/ContentView.swift
  • Sources/TabManager.swift
🛑 Comments failed to post (2)
Sources/TabManager.swift (2)

20-88: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

New git probe orchestration should not keep expanding TabManager.

The new limiter + batched snapshot scheduling logic adds more provider/workstream behavior directly into Sources/TabManager.swift. This increases coupling in an already very large app-target god file and makes isolated testing harder. Please extract this into a dedicated service/package boundary (with injected dependency into TabManager) before adding more logic here.

As per coding guidelines: “Sources/**/*.swift: Flag Provider, auth, protocol, parsing, persistence, logging, or workstream logic that needs isolated fakes, fixtures, or unit tests but is hidden behind app-target globals.”

Also applies to: 2803-2905

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/TabManager.swift` around lines 20 - 88, The
WorkspaceGitMetadataProbeLimiter and related types
(WorkspaceGitMetadataProbeWaiter, cancelledWaiterIds logic) should be moved out
of TabManager into their own service boundary (e.g., WorkspaceGitProbeService or
WorkspaceGitMetadataProbeLimiter in a new file) and exposed via a protocol that
TabManager depends on; create a protocol (e.g., WorkspaceGitProbeLimiting) that
declares acquire()/release() async methods, implement it with the existing
WorkspaceGitMetadataProbeLimiter logic in a new source file, update TabManager
to accept the protocol as an injected dependency (initializer/property) and stop
declaring these types in Sources/TabManager.swift, and update callers/tests to
pass a concrete or fake implementation for isolated unit testing.

80-86: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Cancellation bookkeeping can leak stale waiter IDs.

At Line 85, cancelledWaiterIds.insert(id) runs when the waiter is no longer in waiters. In a cancellation-vs-release race, that can happen after the continuation was already resumed true, leaving a UUID that is never removed. Over time this can grow unbounded under churny cancellation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/TabManager.swift` around lines 80 - 86, cancelWaiter can leak UUIDs
because you insert into cancelledWaiterIds when the waiter isn’t in waiters, but
later the waiter may be removed/resumed elsewhere leaving that id forever; fix
by removing the id from cancelledWaiterIds whenever you actually remove/resume a
waiter. Concretely, in cancelWaiter and in the code paths that remove a waiter
(the block that does waiters.remove(at:) and calls
waiter.continuation.resume(...)), call cancelledWaiterIds.remove(id)
after/resolution so the set cannot grow unbounded; keep the existing insert
branch for races but ensure every successful removal/resume of a waiter calls
cancelledWaiterIds.remove(id).

@austinywang
austinywang merged commit 0c10ab5 into main Jun 5, 2026
24 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — 01ca8d45 Deployed Jun 5, 2026 by vercel[bot]
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.

Renaming a workspace group doesn't update the group name in the title bar (stays "Group 1")

1 participant