feat: repo-level worktree housekeeping#3034
Conversation
Map listManagedWorktrees, worktreeSize, and removeWorktrees through the web environmentApi and fix all vcs mock objects in the affected test files.
Add classifyManagedWorktrees and selectWorktreesForScope pure helpers to worktreeCleanup.ts, along with the WorktreeThreadRef, ClassifiedWorktree, and WorktreeClassification types needed to drive cleanup dialog pre-selection.
…mable total - add worktreeCleanupScope to ServerSettingsPatch so the setting persists through the updateSettings RPC (Schema strips unknown keys otherwise) - totalSelectedBytes now counts only removable rows so the footer matches what will actually be deleted (dirty rows need force)
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
# Conflicts: # apps/web/src/components/Sidebar.tsx
ApprovabilityVerdict: Needs human review This PR introduces a significant new feature for repo-level worktree cleanup, adding new RPC methods, UI components, and user workflows across multiple packages. New features with this scope require human review. Additionally, there is an unresolved medium-severity review comment about incorrect behavior during loading states. You can customize Macroscope's approvability policy. Learn more. |
- normalize separators/trailing slashes when matching worktree paths so live-thread worktrees aren't misclassified as orphaned - resolve real paths symmetrically in listManagedWorktrees so symlinked worktrees dirs match correctly - hold threadRefs in a ref so frequent thread-store updates no longer reset the dialog and discard in-progress selection - surface listManagedWorktrees and removeWorktrees failures via toast + an inline load-error state instead of silently showing "nothing to clean up" - add a .catch with toast to the archived-thread Delete button - route the sidebar "Clean up worktrees" action through the per-member submenu machinery so grouped projects target the right repo
classifyManagedWorktrees matched worktree paths against threads from every environment, so a path collision in another environment could mark a worktree active. Filter both the sidebar and archived-panel thread refs to the environment the dialog is opened for.
…worktrees The sidebar passed only live-thread refs, so archived-thread worktrees opened from it were misclassified as orphaned and pre-selected under the default "orphaned" scope. And active worktrees were filtered out of the dialog entirely, so the "protected" rows were never visible. The dialog now assembles its own thread picture (live store threads + archived snapshots) for its environment, classifies every managed worktree, and renders all rows via derived state — active rows shown locked/protected, selection defaulted per scope and preserved across thread-store updates via per-path overrides. Removal is gated until both worktrees and archived snapshots load. Both call sites drop the now-internal threadRefs prop.
|
screenshots / recordings pls |
The inline `useStore((s) => selectThreadsForEnvironment(s, environmentId))`
returned a new array each render, which defeats useSyncExternalStore's
memoization and caused an infinite render loop ("Maximum update depth
exceeded") when opening the dialog. Wrap it in useShallow like the rest of
the store call sites; the underlying thread objects are cache-stable, so
shallow equality holds and the snapshot stays stable.
|
Screenshots added to OP. |
…leanup - the cleanup dialog now reads the archived-snapshot load error; when it fails, archived worktrees can't be distinguished from orphaned, so it shows the error and disables removal instead of silently offering archived worktrees under the orphaned scope - listManagedWorktrees now fails with a GitCommandError when `git worktree list` exits non-zero, instead of returning an empty list that the UI renders as "Nothing to clean up"
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8ddbdf4. Configure here.
| <DialogFooter> | ||
| <span className="mr-auto text-sm text-muted-foreground"> | ||
| Reclaimable: {formatBytes(total)} | ||
| </span> |
There was a problem hiding this comment.
Reclaimable total before archive load
Medium Severity
While archived thread snapshots are still loading, the dialog body shows “Scanning worktrees…” but the footer still renders Reclaimable from totalSelectedBytes(rows). rows can be built from incomplete threadRefs until useArchivedThreadSnapshots finishes, so archived-only paths may be treated as orphaned and counted until loading completes.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8ddbdf4. Configure here.
removeWorktrees ran `git worktree remove` on any client-supplied path. Add a defense-in-depth check that resolves each path and refuses (per-path) anything not under the server worktrees dir, mirroring listManagedWorktrees.


Screenshots
Summary
Implements the repo-level worktree cleanup requested in #684.
Adds a repo-scoped "Clean up worktrees" action that lists T3 Code-managed git worktrees for a repository, shows each one's on-disk size and a dirty flag, lets the user select and force-remove them, and shows the total reclaimable space upfront.
listManagedWorktrees(porcelain parse filtered to paths underworktreesDir, with a per-worktree dirty check),worktreeSize(recursive filesystem walk, lazily fetched per row and cached client-side), andremoveWorktrees(batch remove with per-pathforceand per-path success/failure outcomes).worktreeCleanupScopesetting (orphaneddefault, ororphaned-archived).WorktreeCleanupDialogwith lazy size loading, a reclaimable-size footer, per-row deselect, and a per-row force toggle for dirty worktrees; opened from a sidebar project context-menu item and from the Settings → Archived Threads panel.A "managed worktree" is any
git worktreewhose path is under the server'sworktreesDir, excluding the main checkout — the reliable identity test since all T3 Code worktrees are created there.Closes #684.
Note
Medium Risk
Batch
git worktree removewith optional force can delete user data; mitigations include managed-dir path checks, active-thread protection, and dirty-row force gating, but misclassification or force misuse could still remove wanted worktrees.Overview
Adds repo-scoped worktree housekeeping: list T3-managed worktrees (under
worktreesDir), show on-disk size and dirty state, and batch-remove with per-pathforceand outcomes.Backend: New contracts/RPC/IPC (
vcs.listManagedWorktrees,vcs.worktreeSize,vcs.removeWorktrees),GitVcsDriverCoreimplementation (porcelain parse + realpath filter, dirty via status/unpushed HEAD, recursive size walk, batch remove guarded to managed paths only), plumbed throughGitWorkflowService, WS handlers, and clientenvironmentApi/wsRpcClient.Web:
WorktreeCleanupDialogclassifies worktrees against live + archived threads (active/archived-only/orphaned), honors newworktreeCleanupScopesetting (orphanedvsorphaned-archived), lazy sizes, reclaimable total, force for dirty rows, and blocks active worktrees. Entry points: sidebar project Clean up worktrees… and Archived Threads Clean up; settings row for scope. Archived thread rows get a visible Delete button.Tests: Driver integration tests for list/size/batch remove/path refusal; web classification and dialog logic tests; contract schema/settings tests.
Reviewed by Cursor Bugbot for commit a8e2927. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add repo-level worktree housekeeping UI and backend RPCs
WorktreeCleanupDialogcomponent accessible from the project context menu in the sidebar and the Archived Threads settings panel, letting users review and remove managed worktrees for a repository.listManagedWorktrees,worktreeSize, andremoveWorktrees— exposed via WebSocket RPCs and wired throughGitVcsDriverCoreandGitWorkflowService.worktreeCleanupScopesetting (orphanedororphaned-archived) controls which classes are selected by default.git statusand a remote-contains check; branches with no upstream remote are treated as dirty.removeWorktreesenforces a path guard, refusing to delete anything outside the configured managed worktrees directory.Macroscope summarized a8e2927.