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 substantial new feature (worktree cleanup dialog, new RPC methods, settings) with 3000+ lines of new code across multiple packages. Additionally, there are unresolved review comments including a security concern about path validation in the batch remove operation. 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.
| }, | ||
| { concurrency: 1 }, | ||
| ); | ||
| return { results }; |
There was a problem hiding this comment.
Batch remove lacks managed path guard
Medium Severity
removeWorktrees runs git worktree remove for every client-supplied path under input.cwd without checking that each path lies under the server worktreesDir, unlike listManagedWorktrees which filters to managed locations only.
Reviewed by Cursor Bugbot for commit 8010b27. Configure here.
|
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.


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 force can delete unpushed work; mitigations include dirty detection, active-thread protection, and explicit force toggles, but filesystem/git edge cases remain.Overview
Adds repo-scoped worktree housekeeping: list T3-managed worktrees under
worktreesDir, show disk size and dirty state, and batch-remove selected paths with optional per-itemforce.Backend exposes three VCS RPCs end-to-end (contracts,
GitVcsDriverCore,GitWorkflowService, WebSocket):listManagedWorktrees(porcelain parse + dirty heuristic),worktreeSize(recursive walk), andremoveWorktrees(per-path outcomes; batch remove refreshes VCS status). A newworktreeCleanupScopesetting (orphanedvsorphaned-archived) controls default pre-selection.Web adds
WorktreeCleanupDialogwith classification against live + archived thread refs (classifyManagedWorktrees/selectWorktreesForScope), lazy sizes, reclaimable total, and force gating for dirty rows. Entry points: sidebar project context menu Clean up worktrees… and Archived Threads Clean up; General settings row for scope. Archived thread rows also get a visible Delete button beside Unarchive.Reviewed by Cursor Bugbot for commit 8ddbdf4. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add repo-level worktree housekeeping with cleanup dialog and VCS RPCs
WorktreeCleanupDialogcomponent accessible from the project context menu in the sidebar and the Archived Threads settings panel, allowing users to review, select, and remove managed worktrees for a repository.active,archived-only, ororphanedrelative to thread refs, with scope controlled by a newworktreeCleanupScopesetting (default:'orphaned').vcs.listManagedWorktrees,vcs.worktreeSize,vcs.removeWorktrees) wired through contracts, server WebSocket handlers, client runtime, andEnvironmentApi.GitVcsDriverCorelists worktrees under the configured worktrees directory, reports dirty state viagit status --porcelain, computes on-disk size recursively, and handles batch removal with per-path outcomes.Macroscope summarized 8ddbdf4.