Skip to content

fix(desktop): hot fix for worktree deletion (still need to refactor)#2929

Merged
AviPeltz merged 3 commits into
mainfrom
fix-changes-sidebar
Mar 27, 2026
Merged

fix(desktop): hot fix for worktree deletion (still need to refactor)#2929
AviPeltz merged 3 commits into
mainfrom
fix-changes-sidebar

Conversation

@AviPeltz
Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz commented Mar 27, 2026

Summary

Enhances the safety mechanism that prevents accidental deletion of external worktrees by implementing a more robust detection algorithm that cross-references git worktrees with database-tracked worktrees.

Problem

The previous implementation relied solely on listExternalWorktrees(), which could miss edge cases where a worktree was incorrectly marked as createdBySuperset in the database. This created a risk of accidentally deleting user-managed external worktrees.

Solution

The improved detection logic now:

  1. Gets all git worktrees from the repository using listExternalWorktrees()
  2. Queries tracked worktrees from the database for the project
  3. Normalizes paths for accurate comparison (handles symlinks via normalizePath())
  4. Determines external status by checking if a worktree:
    • Exists in git
    • Is NOT tracked in our database

A worktree is considered "actually external" only if it exists in git but is not tracked in our DB, regardless of the createdBySuperset flag.

Changes

  • Modified workspace.delete procedure to use enhanced external worktree detection
  • Modified workspace.deleteWorktree procedure with the same safety check
  • Added database queries to get all tracked worktrees for path comparison
  • Improved logging and analytics tracking for safety triggers

Test Plan

  • Test deleting a Superset-created worktree (should succeed)
  • Test deleting an external worktree incorrectly marked as createdBySuperset (should preserve)
  • Verify safety analytics are tracked when mismatch is detected
  • Check that symlinked paths are properly normalized and compared

Related

This addresses the workspace deletion safety concerns raised in previous issues around external worktree preservation.


Summary by cubic

Improves deletion safety by cross-checking git worktrees with DB-tracked paths, and deletes all Superset-tracked worktrees from disk on removal. Applies to both workspace.delete and workspace.deleteWorktree.

  • Bug Fixes

    • Delete from disk for any worktree tracked in the DB; preserve untracked ones.
    • Cross-check git worktrees against DB paths, with normalized paths (handles symlinks).
    • Add logging and analytics when the safety check blocks deletion.
  • Dependencies

    • Bump @superset/desktop to 1.4.3 in bun.lock.

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

Summary by CodeRabbit

  • Bug Fixes
    • Improved workspace/worktree deletion to reliably detect externally-created worktrees by comparing actual repository worktrees with tracked project paths.
    • Preserves disk data for detected external worktrees and emits a safety event when an untracked worktree is found.
    • Removes prior mismatch-based gating so deletions proceed only when confirmed safe.

…check

Enhance the safety mechanism that prevents deletion of external worktrees
by cross-referencing git worktrees with database-tracked worktrees.

Previously, the check relied solely on listExternalWorktrees(), which could
miss cases where a worktree was incorrectly marked as createdBySuperset.

Now the logic:
- Gets all git worktrees from the repository
- Queries all tracked worktrees from the database for the project
- Normalizes paths for accurate comparison (handles symlinks)
- Determines if a worktree is external by checking if it exists in git
  but is NOT tracked in the database

This prevents accidental deletion of external worktrees that may have been
incorrectly flagged, providing an additional layer of safety.

Applied to both workspace.delete and workspace.deleteWorktree procedures.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

The workspace/worktree deletion procedures now always enumerate git worktrees and load tracked worktree paths from the local DB, normalize and compare paths, and treat a path as external only if it exists in git and is not tracked in the DB. External worktrees are preserved and a safety event is emitted; otherwise disk removal proceeds.

Changes

Cohort / File(s) Summary
Worktree Deletion Safety Logic
apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts
Replace prior conditional/race-check logic with always-loading both listExternalWorktrees(project.mainRepoPath) and DB-tracked worktrees, normalize paths, compute isActuallyExternal = existsInGit && !trackedPaths.has(normalizedPath). If external, emit worktree_delete_safety_trigger with reason: "untracked_worktree_detected" and preserve disk; otherwise proceed to remove disk and teardown paths and remove old "createdBySuperset"/mismatch checks and logs.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant TRPC as TRPC Router
  participant Git as Git (listExternalWorktrees)
  participant DB as LocalDB
  participant FS as Filesystem
  participant EE as EventEmitter

  Client->>TRPC: request deleteWorktree / workspace.delete
  TRPC->>Git: listExternalWorktrees(project.mainRepoPath)
  TRPC->>DB: select tracked worktrees for project
  TRPC->>TRPC: normalize paths, compute isActuallyExternal
  alt isActuallyExternal == true
    TRPC->>EE: emit worktree_delete_safety_trigger(reason: "untracked_worktree_detected")
    TRPC->>Client: respond preserved (no disk delete)
  else isActuallyExternal == false
    TRPC->>FS: remove worktree disk (and teardown path if present)
    TRPC->>DB: delete worktree record
    TRPC->>Client: respond success (deleted)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through git and DB tonight,
Checked every path by lantern light,
Untracked ones I left to stay,
Removed the rest and hopped away,
Safe burrows now — all tidy, right?

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The PR title 'fix(desktop): hot fix for worktree deletion (still need to refactor)' is vague and suggests incompleteness, failing to clearly convey the actual change of improving external worktree detection in the deletion safety mechanism. Consider updating the title to reflect the main change: 'fix(desktop): improve external worktree detection in deletion safety check' to make the purpose clear without indicating the work is incomplete or still in development.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering the problem, solution, changes, and test plan as required by the template.

✏️ 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 fix-changes-sidebar

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 27, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

Thank you for your contribution! 🎉

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts (1)

277-299: Extract duplicated “actually external” classification into a shared helper.

The same query/normalize/classify logic now exists in two deletion flows. Consolidating it will reduce drift risk and keep future safety fixes consistent.

♻️ Proposed refactor
+const getTrackedWorktreePathSet = (projectId: string): Set<string> => {
+	const trackedWorktrees = localDb
+		.select({ path: worktrees.path })
+		.from(worktrees)
+		.where(eq(worktrees.projectId, projectId))
+		.all();
+	return new Set(trackedWorktrees.map((wt) => normalizePath(wt.path)));
+};
+
+const isActuallyExternalWorktree = async ({
+	mainRepoPath,
+	projectId,
+	worktreePath,
+}: {
+	mainRepoPath: string;
+	projectId: string;
+	worktreePath: string;
+}): Promise<boolean> => {
+	const allGitWorktrees = await listExternalWorktrees(mainRepoPath);
+	const trackedPaths = getTrackedWorktreePathSet(projectId);
+	const worktreePathNorm = normalizePath(worktreePath);
+	const existsInGit = allGitWorktrees.some(
+		(wt) => normalizePath(wt.path) === worktreePathNorm,
+	);
+	return existsInGit && !trackedPaths.has(worktreePathNorm);
+};
-// Get all git worktrees
-const allGitWorktrees = await listExternalWorktrees(project.mainRepoPath);
-// Get all tracked worktree paths from database for this project
-const trackedWorktrees = localDb
-  .select({ path: worktrees.path })
-  .from(worktrees)
-  .where(eq(worktrees.projectId, project.id))
-  .all();
-const trackedPaths = new Set(
-  trackedWorktrees.map((wt) => normalizePath(wt.path)),
-);
-const worktreePathNorm = normalizePath(worktree.path);
-const existsInGit = allGitWorktrees.some(
-  (wt) => normalizePath(wt.path) === worktreePathNorm,
-);
-const isActuallyExternal =
-  existsInGit && !trackedPaths.has(worktreePathNorm);
+const isActuallyExternal = await isActuallyExternalWorktree({
+	mainRepoPath: project.mainRepoPath,
+	projectId: project.id,
+	worktreePath: worktree.path,
+});

Also applies to: 510-531

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts` around
lines 277 - 299, Duplicate logic that determines whether a worktree is "actually
external" should be extracted into a single helper to avoid drift: create a
function (e.g., isWorktreeActuallyExternal or classifyExternalWorktree) that
accepts the repository path, the worktree path, and the DB instance, calls
listExternalWorktrees(projectMainRepoPath), normalizes paths with normalizePath,
queries tracked paths from the worktrees table (using
localDb.select(...).from(worktrees).where(eq(worktrees.projectId, ...)).all()),
and returns a boolean (or classification) matching the existing
isActuallyExternal logic; then replace the duplicated blocks (including the
occurrence around isActuallyExternal and the similar block at lines ~510-531)
with calls to this new helper (keep references to listExternalWorktrees,
normalizePath, tracked worktrees query, and isActuallyExternal semantics).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts`:
- Around line 277-299: Duplicate logic that determines whether a worktree is
"actually external" should be extracted into a single helper to avoid drift:
create a function (e.g., isWorktreeActuallyExternal or classifyExternalWorktree)
that accepts the repository path, the worktree path, and the DB instance, calls
listExternalWorktrees(projectMainRepoPath), normalizes paths with normalizePath,
queries tracked paths from the worktrees table (using
localDb.select(...).from(worktrees).where(eq(worktrees.projectId, ...)).all()),
and returns a boolean (or classification) matching the existing
isActuallyExternal logic; then replace the duplicated blocks (including the
occurrence around isActuallyExternal and the similar block at lines ~510-531)
with calls to this new helper (keep references to listExternalWorktrees,
normalizePath, tracked worktrees query, and isActuallyExternal semantics).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6980f276-1d12-480d-aaa3-47c8b18f2c58

📥 Commits

Reviewing files that changed from the base of the PR and between 8a6fd78 and 0085c88.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

…uperset

Update deletion logic to remove all worktrees from disk when deleted through
Superset, regardless of whether they were created by Superset or imported as
external worktrees.

Previously:
- Superset-created worktrees (createdBySuperset=true) → deleted from disk
- External worktrees (createdBySuperset=false) → only removed from database

Now:
- All worktrees tracked in Superset → deleted from disk when removed
- Safety check still prevents deletion of untracked worktrees

This provides consistent UX: once a worktree is managed in Superset (even if
originally created externally), deleting it removes it completely.

The safety mechanism still protects against edge cases where a worktree exists
in git but is not properly tracked in the database.

Updated both workspace.delete and workspace.deleteWorktree procedures.
@AviPeltz AviPeltz changed the title fix(desktop): improve external worktree detection in deletion safety check fix(desktop): hot fix for worktree deletion (still need to refactor) Mar 27, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts">

<violation number="1" location="apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts:295">
P0: This external-detection condition misclassifies imported external worktrees as safe-to-delete, which can delete user-managed worktree directories from disk.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +295 to +296
const isActuallyExternal =
existsInGit && !trackedPaths.has(worktreePathNorm);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 27, 2026

Choose a reason for hiding this comment

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

P0: This external-detection condition misclassifies imported external worktrees as safe-to-delete, which can delete user-managed worktree directories from disk.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/lib/trpc/routers/workspaces/procedures/delete.ts, line 295:

<comment>This external-detection condition misclassifies imported external worktrees as safe-to-delete, which can delete user-managed worktree directories from disk.</comment>

<file context>
@@ -267,43 +270,51 @@ export const createDeleteProcedures = () => {
+						const existsInGit = allGitWorktrees.some(
+							(wt) => normalizePath(wt.path) === worktreePathNorm,
+						);
+						const isActuallyExternal =
+							existsInGit && !trackedPaths.has(worktreePathNorm);
+
</file context>
Suggested change
const isActuallyExternal =
existsInGit && !trackedPaths.has(worktreePathNorm);
const isActuallyExternal =
!worktree.createdBySuperset ||
(existsInGit && !trackedPaths.has(worktreePathNorm));
Fix with Cubic

@AviPeltz AviPeltz merged commit 653cb3a into main Mar 27, 2026
20 checks passed
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