Skip to content

Coalesce sidebar git metadata probes - #5402

Merged
austinywang merged 2 commits into
mainfrom
issue-4639-git-metadata-probe-storm
Jun 5, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-4639-git-metadata-probe-storm

Conversation

@austinywang

@austinywang austinywang commented Jun 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Coalesce sidebar git metadata snapshot reads by normalized directory so multiple panels in the same repo share one in-flight index scan.
  • Bound concurrent snapshot reads with an async two-permit limiter.
  • Replace per-byte Foundation String(format:) hex formatting in git index parsing with a byte encoder.

Issue

Verification

  • swift test --package-path Packages/CmuxGit
  • ./scripts/reload-cloud.sh --tag 4639git, cloud pool was full so it fell back to local ./scripts/reload.sh --tag 4639git and succeeded.

Dogfood


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


Note

Medium Risk
Touches sidebar git dirty/branch state and concurrent disk reads; behavior is covered by new coalescing and hex tests but affects many panels sharing one repo.

Overview
Sidebar git metadata initial snapshot work is restructured so multiple panels in the same directory share one in-flight read instead of each firing its own detached probe.

TabManager queues probe keys per normalized directory, runs a single utility task per directory, and fans the resulting snapshot out to every queued panel. A shared WorkspaceGitMetadataProbeLimiter (two concurrent permits, cancellation-aware) caps how many snapshot reads run at once. WorkspaceGitMetadataReading is injectable (defaults to GitMetadataService); snapshot tasks and queued requests are torn down when git watching is disabled or a probe is cleared.

In CmuxGit, git index parsing replaces per-byte String(format:) hex with gitIndexHexString / fixed-width helpers; a test locks object ID and checksum text. TabManagerUnitTests adds a coalescing test with a blocking reader.

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


Summary by cubic

Coalesced sidebar git metadata probes by directory and added a 2-permit async limiter to stop probe storms. Also replaced slow hex encoding in git index parsing with fast lowercase byte encoders to reduce CPU use.

  • Bug Fixes

    • Coalesce initial git snapshot reads per normalized directory so panels share one in-flight scan; fan results to all queued panels.
    • Limit concurrent snapshot reads to 2 with a cancellation-aware limiter.
    • Queue per-directory requests; cancel queued reads when panels close, directories change, or git watch is disabled.
    • Clear snapshot tasks and queues on workspace unload.
    • Addresses Production cmux beachballs from git metadata probe storm and 18 GB graphics footprint #4639.
  • Refactors

    • Replaced String(format:) hex in git index parsing with byte encoders and fixed-width helpers in CmuxGit (object IDs and checksums).
    • Introduced WorkspaceGitMetadataReading for injection; added unit tests for coalescing and hex stability.

Written for commit a23bf2c. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Performance Improvements

    • Workspace git metadata probing now limits concurrent probes, coalesces requests per directory, and cancels/cleans up queued tasks to reduce redundant reads and latency.
  • Bug Fixes

    • Hex encoding for git index fields now produces consistent lowercase fixed-width hex strings so object IDs and signatures decode unchanged.
  • Tests

    • Added tests validating index hex decoding and that simultaneous probes for the same directory share a single initial metadata read.

@vercel

vercel Bot commented Jun 4, 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 12:22am
cmux-staging Building Building Preview, Comment Jun 5, 2026 12:22am

@austinywang

Copy link
Copy Markdown
Contributor Author

@codex review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds reusable hex-encoding helpers for git index parsing and implements a protocol-backed, concurrency-limited, coalescing workspace git metadata snapshot pipeline in TabManager with tests validating probe coalescing.

Changes

Git Index Hex Encoding Refactor

Layer / File(s) Summary
Hex encoding helpers and usage updates
Packages/CmuxGit/Sources/CmuxGit/Parsing/GitMetadataService+Index.swift, Packages/CmuxGit/Tests/CmuxGitTests/GitMetadataServiceTests.swift
Adds gitIndexHexAlphabet, gitIndexHexString, and gitIndexFixedWidthHexString; replaces per-byte String(format:) assembly for object-id, trailing checksum, and content-signature with these helpers. Adds test asserting decoded objectID and signature hex match original values.

Concurrency-Limited Batched Git Metadata Snapshot Probing

Layer / File(s) Summary
Concurrency protocol and limiter infrastructure
Sources/TabManager.swift
Adds WorkspaceGitMetadataReading protocol, makes GitMetadataService conform, and implements WorkspaceGitMetadataProbeLimiter actor with cancellation-aware waiter handling.
State tracking and dependency injection wiring
Sources/TabManager.swift
Adds WorkspaceGitSnapshotProbeRequest type, per-directory queued-request and in-flight-task dictionaries, stores workspaceGitMetadataReader on TabManager, and extends initializer to accept it (fallback to gitMetadataService).
Snapshot batching and probe coalescing core logic
Sources/TabManager.swift
Refactors probe attempts to enqueue per-probe snapshot requests and start a single per-directory detached task (bounded by the limiter) that fetches one snapshot via the injected reader and applies it to all queued probe keys. Converts initialWorkspaceGitMetadataSnapshot to a static async function using the reader.
Task cancellation and lifecycle cleanup
Sources/TabManager.swift
Cancels queued and in-flight per-directory snapshot tasks during deinit and when git-watching is disabled; ensures clearing a probe removes queued snapshot state.
Test infrastructure and coalescing validation
cmuxTests/TabManagerUnitTests.swift
Adds CmuxGit import, BlockingWorkspaceGitMetadataReader test actor that blocks responses and tracks concurrency, restoreUserDefaultForTabManagerTests helper, and testSameDirectoryInitialGitMetadataProbesShareOneSnapshotRead validating coalescing.

Sequence Diagram

sequenceDiagram
  participant TabManager
  participant Limiter as WorkspaceGitMetadataProbeLimiter
  participant Reader as WorkspaceGitMetadataReader
  participant Storage as SnapshotQueue

  rect rgba(100, 149, 237, 0.5)
    Note over TabManager,Storage: First probe for directory
    TabManager->>Storage: enqueue probe request
    TabManager->>Limiter: request permit
    Limiter-->>TabManager: permit granted
    TabManager->>Reader: fetch workspace metadata(reader.workspaceMetadata(for:))
    Reader-->>TabManager: return snapshot
  end

  rect rgba(144, 238, 144, 0.5)
    Note over TabManager,Storage: Concurrent probes for same directory
    TabManager->>Storage: enqueue additional probe request(s)
    Note over Storage: requests coalesced into single fetch
  end

  rect rgba(255, 182, 193, 0.5)
    Note over TabManager,Storage: Apply snapshot to queued probes
    TabManager->>TabManager: apply snapshot to all queued probe keys
    TabManager->>Limiter: release permit
    Limiter-->>TabManager: permit available
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#5363: Overlaps TabManager git-metadata probe scheduling and task orchestration changes.
  • manaflow-ai/cmux#5277: Prior work touching CmuxGit index parsing that relates to these hex-encoding updates.

Poem

🐰 I nibbled nibbles, cached each hex char bright,

Turned bytes into lowercase light in one swift bite.
One snapshot shared, panels waited in line,
The limiter hummed, coalescing calls just fine.
A happy rabbit hops — the probes now align.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (4 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Concurrency ❌ Error PR introduces fire-and-forget Tasks with meaningful lifecycle: in limiter.onCancel calling cancelWaiter and in git snapshot defer calling limiter.release(). Make limiter.release() async, or restructure onCancel to avoid fire-and-forget Tasks calling actor methods.
Cmux Swift File And Package Boundaries ❌ Error TabManager.swift (9720 lines) violates boundaries: >800 lines with mixed UI/state/persistence/networking; >250 lines added without extraction; snapshot coalescing logic belongs in CmuxGit package. Extract WorkspaceGitMetadataProbeLimiter and coalescing logic to CmuxGit package coordinator; reduce TabManager by >200 lines; update budget after extraction.
Cmux Swift Logging ❌ Error PR adds private let tabManagerLogger = Logger(...) in file with @MainActor class. Must be nonisolated private let per swift-logging.md MainActor isolation rules. Declare as nonisolated private let tabManagerLogger = Logger(subsystem: "com.cmuxterm.app", category: "TabManager").
Cmux Architecture Rethink ❌ Error PR adds mutable caches (workspaceGitSnapshotRequestsByDirectory, workspaceGitCleanIndexSignatureByKey) creating duplicate ownership of git state already owned by Workspace.panelGitBranches. Consolidate caches to single owner; prove dirty-state invariant prevents inconsistency between panelGitBranches and cached signatures.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (13 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed All production code changes properly follow Swift 6 actor isolation rules: @MainActor TabManager, Sendable service protocol, new actor limiter, and immutable Sendable structs.
Cmux Swift Blocking Runtime ✅ Passed WorkspaceGitMetadataProbeLimiter uses actor and CheckedContinuation for async signaling. No forbidden blocking operations (DispatchSemaphore, Task.sleep, NSLock, DispatchQueue.main.sync) introduced.
Cmux No Hacky Sleeps ✅ Passed All changes are Swift code. The rule applies only to TypeScript, JavaScript, shell, and non-Swift build/runtime scripts; Swift timing primitives are covered by a separate rule per scope statement.
Cmux Algorithmic Complexity ✅ Passed Hex encoding changes use constant-size operations (20 bytes). TabManager changes use O(N) scans where N=panels in workspace, not workspaces; in non-hot paths (git probes, workspace close).
Cmux Swift @Concurrent ✅ Passed File I/O work lacks @concurrent correctly (feature disabled); UI work has explicit Task.detached hops; no invalid @concurrent annotations found.
Cmux User-Facing Error Privacy ✅ Passed PR adds git metadata hex encoding helpers and coalescing probe logic. No user-facing error messages or sensitive information exposed. Test-only code is allowed per rules.
Cmux Full Internationalization ✅ Passed PR contains no user-facing text requiring localization: only private hex encoding helpers, internal protocol/actor refactoring, and test code.
Cmux Swiftui State Layout ✅ Passed PR modifies only existing legacy TabManager ObservableObject; no new @Published/@StateObject/@observable state, no GeometryReader changes, no store references in list rows, no mutations in render.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds git metadata probing infrastructure and hex encoding utilities only; no user-visible NSWindow, NSPanel, NSWindowController, or SwiftUI Window/WindowGroup changes.
Title check ✅ Passed The title clearly and concisely summarizes the main change: coalescing sidebar git metadata probes to share one snapshot read instead of multiple independent reads.
Description check ✅ Passed The description covers the required template sections: Summary (what changed and why), Testing (swift test --package-path Packages/CmuxGit and deployment verification), and includes related issue reference. Checklist items are mostly addressed through testing verification, though the formal checklist boxes are not checked.
✨ 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-4639-git-metadata-probe-storm

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.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 4, 2026

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/CmuxGit/Sources/CmuxGit/Parsing/GitMetadataService+Index.swift (1)

270-275: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider applying the same hex encoding refactor to gitIndexFileSignature for consistency.

This function still uses the old map { String(format: "%02x", $0) }.joined() pattern, which could be replaced with gitIndexHexString(data.suffix(20)) for consistency and performance.

♻️ Optional consistency refactor
-        return data.suffix(20).map { String(format: "%02x", $0) }.joined()
+        return gitIndexHexString(data.suffix(20))
🤖 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 `@Packages/CmuxGit/Sources/CmuxGit/Parsing/GitMetadataService`+Index.swift
around lines 270 - 275, Replace the manual hex encoding in nonisolated static
func gitIndexFileSignature with the shared helper: when reading the last 20
bytes (currently data.suffix(20)), pass that slice to gitIndexHexString(...) and
return its result instead of using map { String(format: "%02x", $0) }. Keep the
same guard behavior and return types; reference gitIndexFileSignature and
gitIndexHexString to locate the change.
🤖 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 `@cmuxTests/TabManagerUnitTests.swift`:
- Around line 98-103: waitForCallCount(_:) currently appends a checked
continuation to callCountWaiters with no cancellation path; make it cancellable
by using a throwing continuation (withCheckedThrowingContinuation) or a
withTaskCancellationHandler so that when the awaiting Task is cancelled you
remove the corresponding (expected, continuation) entry from callCountWaiters
and resume the continuation with a CancellationError. Update the function to
append the waiter atomically, and ensure both normal completion (when callCount
reaches expected) and cancellation resume the continuation and remove the waiter
to avoid retaining reader/expectations.
- Around line 822-829: After the existing waitForCondition that asserts all
panels' branches are "main", add a final assertion to ensure no additional
workspaceMetadata reads were started after release: assert observedCallCount ==
1 (or an equivalent idle check) to lock the coalescing contract; locate the test
block that calls reader.releaseAll(), the waitForCondition that checks panelIds
and workspace.panelGitBranches, and add the observedCallCount assertion
immediately after that branch assertion to prevent delayed duplicate probe
startups.

In `@Packages/CmuxGit/Sources/CmuxGit/Parsing/GitMetadataService`+Index.swift:
- Around line 204-212: The helper function gitIndexHexString is currently
package-visible but appears only used within this file; make its declaration
private to restrict scope by changing its signature to private nonisolated
static func gitIndexHexString<S: Sequence>(_ bytes: S) -> String where S.Element
== UInt8 so it documents internal usage and prevents accidental external
access—ensure there are no external callers before making it private.

In `@Sources/TabManager.swift`:
- Around line 2814-2844: The batching key is using expectedDirectory so panels
inside the same repo launch separate snapshot tasks; update the logic to resolve
the repository root and use that as the batching key instead of
expectedDirectory (e.g. call the existing workspaceMetadata(for:) or add a
lightweight repo-root resolver on WorkspaceGitMetadataReading to get repoRoot)
and then replace uses of
workspaceGitSnapshotRequestsByDirectory[expectedDirectory] and
workspaceGitSnapshotTasksByDirectory[expectedDirectory] with the repoRoot key so
all dirs in one repo share the same Task and the same
WorkspaceGitMetadataProbeLimiter permit; ensure
initialWorkspaceGitMetadataSnapshot(for: expectedDirectory, reader:) and any
reader calls use the resolved repoRoot or continue to pass the original dir as
needed but only the queue/task/limiter are keyed by repoRoot.

---

Outside diff comments:
In `@Packages/CmuxGit/Sources/CmuxGit/Parsing/GitMetadataService`+Index.swift:
- Around line 270-275: Replace the manual hex encoding in nonisolated static
func gitIndexFileSignature with the shared helper: when reading the last 20
bytes (currently data.suffix(20)), pass that slice to gitIndexHexString(...) and
return its result instead of using map { String(format: "%02x", $0) }. Keep the
same guard behavior and return types; reference gitIndexFileSignature and
gitIndexHexString to locate the change.
🪄 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: b2e51c72-c2a1-4040-baf6-89915edbce0b

📥 Commits

Reviewing files that changed from the base of the PR and between 5b6325d and 753b28b.

📒 Files selected for processing (4)
  • Packages/CmuxGit/Sources/CmuxGit/Parsing/GitMetadataService+Index.swift
  • Packages/CmuxGit/Tests/CmuxGitTests/GitMetadataServiceTests.swift
  • Sources/TabManager.swift
  • cmuxTests/TabManagerUnitTests.swift

Comment on lines +98 to +103
func waitForCallCount(_ expected: Int) async {
guard callCount < expected else { return }
await withCheckedContinuation { continuation in
callCountWaiters.append((expected, continuation))
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make waitForCallCount(_:) cancellable.

Line 813 starts an unstructured task that waits for call count 2 under an inverted expectation. When coalescing works, that count is never reached, so this helper leaves the task suspended forever because the stored continuation has no cancellation/removal path. That retains reader and the expectation past test completion.

Also applies to: 121-130

🤖 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 `@cmuxTests/TabManagerUnitTests.swift` around lines 98 - 103,
waitForCallCount(_:) currently appends a checked continuation to
callCountWaiters with no cancellation path; make it cancellable by using a
throwing continuation (withCheckedThrowingContinuation) or a
withTaskCancellationHandler so that when the awaiting Task is cancelled you
remove the corresponding (expected, continuation) entry from callCountWaiters
and resume the continuation with a CancellationError. Update the function to
append the waiter atomically, and ensure both normal completion (when callCount
reaches expected) and cancellation resume the continuation and remove the waiter
to avoid retaining reader/expectations.

Comment thread cmuxTests/TabManagerUnitTests.swift
Comment thread Packages/CmuxGit/Sources/CmuxGit/Parsing/GitMetadataService+Index.swift Outdated
Comment thread Sources/TabManager.swift Outdated
Comment on lines +2814 to +2844
workspaceGitSnapshotRequestsByDirectory[expectedDirectory, default: []].append(
WorkspaceGitSnapshotProbeRequest(
probeKey: probeKey,
isLastAttempt: isLastAttempt
)
)
guard workspaceGitSnapshotTasksByDirectory[expectedDirectory] == nil else {
#if DEBUG
cmuxDebugLog(
"workspace.gitProbe.joinSnapshot dir=\(expectedDirectory) " +
"queued=\(workspaceGitSnapshotRequestsByDirectory[expectedDirectory]?.count ?? 0)"
)
#endif
return
}

let reader = workspaceGitMetadataReader
workspaceGitSnapshotTasksByDirectory[expectedDirectory] = Task.detached(priority: .utility) { [weak self] in
let didAcquirePermit = await WorkspaceGitMetadataProbeLimiter.shared.acquire()
guard didAcquirePermit else { return }
defer {
Task {
await WorkspaceGitMetadataProbeLimiter.shared.release()
}
}

guard !Task.isCancelled else { return }
let snapshot = await Self.initialWorkspaceGitMetadataSnapshot(
for: expectedDirectory,
reader: reader
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Batch by repository root, not panel cwd.

This queue is keyed on expectedDirectory, but workspaceMetadata(for:) resolves the enclosing repository before it reads index/head state. Panels at /repo and /repo/subdir therefore still launch separate snapshot tasks and consume separate limiter permits, so the common multi-panel same-repo storm is not actually coalesced here. Key the batch/limiter on resolved repository identity (or add a lightweight repo-root resolver to WorkspaceGitMetadataReading) so all directories inside one repo share a single snapshot read.

Also applies to: 3166-3171

🤖 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 2814 - 2844, The batching key is using
expectedDirectory so panels inside the same repo launch separate snapshot tasks;
update the logic to resolve the repository root and use that as the batching key
instead of expectedDirectory (e.g. call the existing workspaceMetadata(for:) or
add a lightweight repo-root resolver on WorkspaceGitMetadataReading to get
repoRoot) and then replace uses of
workspaceGitSnapshotRequestsByDirectory[expectedDirectory] and
workspaceGitSnapshotTasksByDirectory[expectedDirectory] with the repoRoot key so
all dirs in one repo share the same Task and the same
WorkspaceGitMetadataProbeLimiter permit; ensure
initialWorkspaceGitMetadataSnapshot(for: expectedDirectory, reader:) and any
reader calls use the resolved repoRoot or continue to pass the original dir as
needed but only the queue/task/limiter are keyed by repoRoot.

@greptile-apps

greptile-apps Bot commented Jun 4, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR coalesces git metadata initial snapshot reads in the sidebar so multiple panels pointing at the same directory share a single in-flight index scan, bounded by a new two-permit async WorkspaceGitMetadataProbeLimiter. It also replaces per-byte String(format:"%02x",…) hex encoding in git index parsing with purpose-built byte-array encoders.

  • Coalescing: TabManager queues per-directory WorkspaceGitSnapshotProbeRequest entries and launches one Task.detached per directory; when the snapshot lands on the main actor, applyWorkspaceGitMetadataSnapshotBatch fans it out to every queued panel. Requests are removed and tasks cancelled when probes are cleared or git watching is disabled.
  • Limiter: WorkspaceGitMetadataProbeLimiter is a new actor-based semaphore (default 2 permits) with a cancellation-aware onCancel/cancelWaiter path; the permit is released via defer { Task { await release() } } inside the snapshot task.
  • Hex encoding: gitIndexHexString and gitIndexFixedWidthHexString replace String(format:) calls in index parsing and checksum computation for lower allocation overhead; a new test pins object-ID and checksum output.

Confidence Score: 4/5

Safe to merge with awareness of two open concurrency issues in WorkspaceGitMetadataProbeLimiter captured in prior review threads.

The coalescing logic itself is solid: snapshot tasks are correctly launched once per directory, fanned out to all queued panels via applyWorkspaceGitMetadataSnapshotBatch, and torn down through removeWorkspaceGitSnapshotRequest / cancelAllWorkspaceGitSnapshotTasks on all production paths. The hex encoding rewrite is a straightforward, correct replacement with no parsing side-effects. The two concurrency concerns flagged in prior review threads — stale cancelledWaiterIds accumulation in the limiter under a probe-storm + rapid-cancel scenario, and the direct reference to WorkspaceGitMetadataProbeLimiter.shared inside the detached task body preventing per-test isolation — remain unaddressed in this revision.

Sources/TabManager.swift — specifically WorkspaceGitMetadataProbeLimiter and the Task.detached body in enqueueWorkspaceGitMetadataSnapshotRequest.

Important Files Changed

Filename Overview
Sources/TabManager.swift Introduces WorkspaceGitMetadataProbeLimiter actor, three new per-directory tracking dictionaries, and coalesced snapshot task dispatch. Core logic is sound; two concurrency issues in the limiter are captured in open review threads (stale cancelledWaiterIds accumulation and non-injectable singleton).
Packages/CmuxGit/Sources/CmuxGit/Parsing/GitMetadataService+Index.swift Replaces four String(format:"%02x",…) hex call-sites with gitIndexHexString and gitIndexFixedWidthHexString; both encoders are correct and nonisolated static, with no change to parsing semantics.
Packages/CmuxGit/Tests/CmuxGitTests/GitMetadataServiceTests.swift Adds indexHexFieldsDecodeWithoutChangingObjectAndSignatureText to verify object-ID and checksum hex stability; straightforward and deterministic.
cmuxTests/TabManagerUnitTests.swift Adds BlockingWorkspaceGitMetadataReader actor and testSameDirectoryInitialGitMetadataProbesShareOneSnapshotRead; the negative-expectation window (0.2 s) is short and could be flaky on slow CI, but the overall test structure correctly exercises coalescing.

Sequence Diagram

sequenceDiagram
    participant TM as TabManager (MainActor)
    participant LIM as WorkspaceGitMetadataProbeLimiter (actor)
    participant TASK as Task.detached (utility)
    participant RDR as WorkspaceGitMetadataReader

    Note over TM: Panel A, B, C all in /repo
    TM->>TM: enqueueSnapshotRequest(panelA, /repo)
    TM->>TASK: create snapshot task for /repo
    TM->>TM: enqueueSnapshotRequest(panelB, /repo)
    Note over TM: task exists → join queue
    TM->>TM: enqueueSnapshotRequest(panelC, /repo)
    Note over TM: task exists → join queue

    TASK->>LIM: acquire() [blocks if 2 already active]
    LIM-->>TASK: true (permit granted)
    TASK->>RDR: workspaceMetadata(for: /repo)
    RDR-->>TASK: GitWorkspaceMetadata

    TASK->>TM: "MainActor.run { applyBatch(snapshot, /repo) }"
    TM->>TM: removeTask(/repo), dequeue [A,B,C]
    TM->>TM: applySnapshot → panelA
    TM->>TM: applySnapshot → panelB
    TM->>TM: applySnapshot → panelC

    TASK->>LIM: release() [via defer Task]
    Note over LIM: activeCount-- or hand to next waiter
Loading

Reviews (2): Last reviewed commit: "fix: coalesce sidebar git metadata probe..." | Re-trigger Greptile

Comment thread Sources/TabManager.swift
Comment on lines +80 to +87
private func cancelWaiter(id: UUID) {
if let index = waiters.firstIndex(where: { $0.id == id }) {
let waiter = waiters.remove(at: index)
waiter.continuation.resume(returning: false)
} else {
cancelledWaiterIds.insert(id)
}
}

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 Stale UUID accumulation in cancelledWaiterIds

There is a race window where release() can dequeue a waiter from waiters and resume its continuation with true before the onCancel-spawned cancelWaiter task runs on the actor. When cancelWaiter eventually executes, it finds the ID absent from waiters and inserts it into cancelledWaiterIds. That ID is now permanently stale: release() only cleans cancelledWaiterIds entries for IDs it encounters inside the waiters loop, so an ID for a waiter that was already dequeued is never evicted. Under a probe storm followed by rapid panel closes, cancelledWaiterIds can accumulate unboundedly.

Rule Used: Flag new blocking or timing-based synchronization ... (source)

Comment thread Sources/TabManager.swift
Comment on lines +31 to +32
actor WorkspaceGitMetadataProbeLimiter {
static let shared = WorkspaceGitMetadataProbeLimiter(limit: 2)

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 Non-injectable singleton for the permit limiter

WorkspaceGitMetadataReading was correctly made injectable (and the test stubs it out), but WorkspaceGitMetadataProbeLimiter.shared is a module-level singleton referenced directly inside the Task.detached body. This means the limiter's 2-permit semantics cannot be varied or isolated in tests — all tests share its live state. If two test cases that exercise the probe path run concurrently, they compete for the same 2 permits. Passing the limiter through TabManager's initializer (with the shared instance as the default) would mirror the pattern already applied to workspaceGitMetadataReader and eliminate the shared mutable singleton.

Rule Used: Flag Swift fixes that patch symptoms while leaving... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 4 files

You’re at about 99% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Re-trigger cubic

coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 5, 2026

@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 2871-2884: The current removeWorkspaceGitSnapshotRequest(for:) is
inefficient because it scans all keys; add and use a reverse index (e.g.,
workspaceGitSnapshotDirectoryByProbeKey: [WorkspaceGitProbeKey: String]) that
maps each WorkspaceGitProbeKey to its directory when requests are enqueued,
update that mapping wherever requests are added and cleared, and change
removeWorkspaceGitSnapshotRequest(for:) to look up the directory from
workspaceGitSnapshotDirectoryByProbeKey, fetch and mutate
workspaceGitSnapshotRequestsByDirectory[directory] to remove the matching probe,
remove the directory entries from workspaceGitSnapshotRequestsByDirectory and
workspaceGitSnapshotTasksByDirectory and cancel the task if empty, and finally
remove the probeKey from the reverse index; ensure all enqueue/clear points
maintain the reverse map to keep consistency.
- Around line 2831-2860: The detached Task can still run its MainActor closure
after cancellation and clobber a newer batch; add a cancellation guard inside
the MainActor.run closure (before calling
applyWorkspaceGitMetadataSnapshotBatch) so the closure returns early when
Task.isCancelled, ensuring the stale task does not remove/overwrite entries in
workspaceGitSnapshotTasksByDirectory or workspaceGitSnapshotRequestsByDirectory;
update the closure that currently calls
applyWorkspaceGitMetadataSnapshotBatch(...) (the Task.detached { ... await
MainActor.run { [weak self] in ... } }) to check Task.isCancelled at the start
of that MainActor block and skip calling applyWorkspaceGitMetadataSnapshotBatch
when cancelled.
🪄 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: 0ee96701-6473-44d8-accb-fff0e9b2ccdd

📥 Commits

Reviewing files that changed from the base of the PR and between 753b28b and 6fc0594.

📒 Files selected for processing (4)
  • Packages/CmuxGit/Sources/CmuxGit/Parsing/GitMetadataService+Index.swift
  • Packages/CmuxGit/Tests/CmuxGitTests/GitMetadataServiceTests.swift
  • Sources/TabManager.swift
  • cmuxTests/TabManagerUnitTests.swift

Comment thread Sources/TabManager.swift
Comment thread Sources/TabManager.swift
@austinywang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

This branch was successfully deployed

1 active deployment
Preview – cmux — a23bf2cb 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.

1 participant