Skip to content

fix(desktop): simplify tracked worktree recovery#2973

Open
Kitenite wants to merge 1 commit intomainfrom
kitenite/recover-workspace-pr
Open

fix(desktop): simplify tracked worktree recovery#2973
Kitenite wants to merge 1 commit intomainfrom
kitenite/recover-workspace-pr

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Mar 29, 2026

Summary

  • rebuild the moved tracked worktree recovery work from origin/main as a compact implementation
  • keep read paths inspect-only, and only auto-normalize the stored path when Git already knows the worktree's current location
  • add an explicit repairTrackedWorktreePath mutation plus sidebar/workspace recovery actions that prompt for the moved folder
  • prevent terminal and git-status flows from assuming a stale tracked worktree path

Validation

  • bun run --filter @superset/desktop typecheck
  • bunx biome check apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts apps/desktop/src/lib/trpc/routers/terminal/terminal.ts apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts apps/desktop/src/lib/trpc/routers/workspaces/procedures/repair.ts apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts apps/desktop/src/lib/trpc/routers/workspaces/utils/repair-worktree-path.ts apps/desktop/src/renderer/react-query/workspaces/useRecoverTrackedWorktree.ts apps/desktop/src/renderer/react-query/workspaces/index.ts apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/types.ts apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceList/WorkspaceList.tsx apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceContextMenu.tsx apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx 'apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx'

Notes


Summary by cubic

Add an explicit tracked worktree recovery flow that resolves moved Git worktrees, prompts users to repair paths, and prevents terminal/status from using stale paths. This replaces the auto-repair POC with a simple, user-driven flow.

  • New Features

    • Added workspaces.repairTrackedWorktreePath and a compact resolver in repair-worktree-path (resolveWorktreePath*, repairTrackedWorktreePath) that uses git worktree repair.
    • Workspace queries now expose repairState, repairMessage, repairCommand, and existsOnDisk to drive UI.
    • New recovery UI: sidebar warning + context menu “Recover worktree”, full-screen recovery panel on the workspace page, and useRecoverTrackedWorktree to guide path selection.
  • Bug Fixes

    • Terminal and Git status now resolve worktree paths on demand and avoid stale worktrees.path; errors include recovery guidance.
    • Stored path auto-normalizes only when Git reports the current location; terminals get a pathChanged flag.
    • getWorkspaceCwd, PR status/comments, and worktree name resolution now use the resolved path.

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

Summary by CodeRabbit

  • New Features
    • Added automatic recovery functionality for worktrees that have been moved or become disconnected from their original locations.
    • Added visual warning indicators in the workspace sidebar to identify worktrees requiring recovery.
    • Added "Recover worktree" action with integrated directory picker to help users locate and re-register relocated repositories.
    • Enhanced workspace status tracking with improved visibility into disk presence and repair status.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

The pull request introduces a worktree path repair system that detects when Git worktrees have been moved or are missing on disk, enables resolution through Git repair utilities, and provides user-facing recovery actions in the sidebar and workspace page with command display and clipboard copy functionality.

Changes

Cohort / File(s) Summary
Worktree path resolution and repair utilities
apps/desktop/src/lib/trpc/routers/workspaces/utils/repair-worktree-path.ts, apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
New module implementing core path resolution logic with three outcome states (resolved, repair_required, missing), repair metadata builders, and path validation/persistence. Added git utility to execute git worktree repair operations.
TRPC procedures — path resolution
apps/desktop/src/lib/trpc/routers/terminal/terminal.ts, apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
Converted worktree path lookups from direct DB access to async resolution via repair utilities. Terminal router now tracks pathChanged metadata; query procedures expose new existsOnDisk and repair state fields to clients.
TRPC procedures — git operations
apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts
Updated three procedures (refreshGitStatus, getGitHubStatus, getGitHubPRComments) to resolve worktree paths before invoking git operations; returns null/empty gracefully when path is unresolved.
TRPC repair procedure and router
apps/desktop/src/lib/trpc/routers/workspaces/procedures/repair.ts, apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
New repair procedure exposed as repairTrackedWorktreePath mutation; validates workspace is a tracked worktree and delegates to utility function. Registered in main workspaces router.
React Query hook
apps/desktop/src/renderer/react-query/workspaces/useRecoverTrackedWorktree.ts, apps/desktop/src/renderer/react-query/workspaces/index.ts
New custom hook coordinating directory picker and repair mutation, with invalidation of workspace/terminal caches and success/error toast notifications.
UI components — sidebar
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/types.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceList/WorkspaceList.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/...
Extended workspace data types with repair metadata; pass-through of repair props in list component; added warning icon with tooltip and context menu entry for worktree recovery in list item.
Workspace page
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
Integrated recovery view that displays when repairState === "repair_required", with "Recover worktree" action button and optional repair command copy functionality.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as UI Component
    participant Hook as Recovery Hook
    participant Picker as Directory Picker
    participant TRPC as TRPC Server
    participant Git as Git
    participant DB as Local Database
    
    User->>UI: Click "Recover worktree"
    UI->>Hook: recoverTrackedWorktree()
    Hook->>Picker: Open directory picker
    User->>Picker: Select new worktree path
    Picker-->>Hook: selectedPath
    Hook->>TRPC: repairTrackedWorktreePath({workspaceId, selectedPath})
    TRPC->>DB: getWorkspace(workspaceId)
    DB-->>TRPC: workspace (type: "worktree")
    TRPC->>Git: git worktree repair <selectedPath>
    Git-->>TRPC: success
    TRPC->>Git: Detect Git root & branch
    Git-->>TRPC: branch info
    TRPC->>DB: Update worktrees.path to selectedPath
    DB-->>TRPC: success
    TRPC-->>Hook: {path, pathChanged: true}
    Hook->>Hook: Invalidate workspace caches
    Hook-->>UI: Show success toast
    UI-->>User: Recovery complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A worktree moved, the path forgotten here,
But rabbit utilities make repair so clear!
With git repair and database care,
Lost worktrees return—magic in the air! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a simplified tracked worktree recovery flow instead of the broader auto-repair POC.
Description check ✅ Passed The description includes a clear summary, validation steps, and notes. However, it lacks explicitly filled sections from the template (Related Issues, Type of Change, Testing, Screenshots, Additional 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 kitenite/recover-workspace-pr

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.

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.

4 issues found across 14 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/utils/repair-worktree-path.ts">

<violation number="1" location="apps/desktop/src/lib/trpc/routers/workspaces/utils/repair-worktree-path.ts:134">
P1: The existence fast-path can incorrectly resolve a tracked worktree to the main repository path. Exclude mainRepoPath in this check so stale/invalid paths still go through repair resolution.</violation>
</file>

<file name="apps/desktop/src/renderer/react-query/workspaces/useRecoverTrackedWorktree.ts">

<violation number="1" location="apps/desktop/src/renderer/react-query/workspaces/useRecoverTrackedWorktree.ts:43">
P2: Avoid empty `catch {}` here; keep at least warning-level diagnostics so unexpected failures aren’t silently discarded.

(Based on your team's feedback about avoiding empty catch blocks.) [FEEDBACK_USED]</violation>
</file>

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

<violation number="1" location="apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts:239">
P2: Use a cross-platform basename helper instead of splitting on "/". Windows paths use "\\", so this can return the full path instead of the folder name.

(Based on your team's feedback about using cross-platform path utilities instead of split.) [FEEDBACK_USED]</violation>
</file>

<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx">

<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx:487">
P2: Handle errors from `recoverTrackedWorktree()` instead of discarding its promise with `void`; otherwise a failed directory-selection mutation can become an unhandled rejection.

(Based on your team's feedback about awaiting/catching async calls to avoid unhandled promise rejections.) [FEEDBACK_USED]</violation>
</file>

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

};
}

if (existsSync(context.worktree.path)) {
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

P1: The existence fast-path can incorrectly resolve a tracked worktree to the main repository path. Exclude mainRepoPath in this check so stale/invalid paths still go through repair resolution.

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/utils/repair-worktree-path.ts, line 134:

<comment>The existence fast-path can incorrectly resolve a tracked worktree to the main repository path. Exclude mainRepoPath in this check so stale/invalid paths still go through repair resolution.</comment>

<file context>
@@ -0,0 +1,398 @@
+		};
+	}
+
+	if (existsSync(context.worktree.path)) {
+		return {
+			pathChanged: false,
</file context>
Fix with Cubic

workspaceId,
selectedPath: result.path,
});
} catch {
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

P2: Avoid empty catch {} here; keep at least warning-level diagnostics so unexpected failures aren’t silently discarded.

(Based on your team's feedback about avoiding empty catch blocks.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/react-query/workspaces/useRecoverTrackedWorktree.ts, line 43:

<comment>Avoid empty `catch {}` here; keep at least warning-level diagnostics so unexpected failures aren’t silently discarded.

(Based on your team's feedback about avoiding empty catch blocks.) </comment>

<file context>
@@ -0,0 +1,52 @@
+				workspaceId,
+				selectedPath: result.path,
+			});
+		} catch {
+			// Mutation onError already surfaces the failure to the user.
+		}
</file context>
Fix with Cubic


const worktreeName = worktree.path.split("/").pop() ?? worktree.branch;
const resolvedPath = await resolveWorktreePathWithRepair(worktree.id);
const worktreeName = resolvedPath?.split("/").pop() ?? worktree.branch;
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

P2: Use a cross-platform basename helper instead of splitting on "/". Windows paths use "\", so this can return the full path instead of the folder name.

(Based on your team's feedback about using cross-platform path utilities instead of split.)

View Feedback

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/git-status.ts, line 239:

<comment>Use a cross-platform basename helper instead of splitting on "/". Windows paths use "\\", so this can return the full path instead of the folder name.

(Based on your team's feedback about using cross-platform path utilities instead of split.) </comment>

<file context>
@@ -217,7 +235,8 @@ export const createGitStatusProcedures = () => {
 
-				const worktreeName = worktree.path.split("/").pop() ?? worktree.branch;
+				const resolvedPath = await resolveWorktreePathWithRepair(worktree.id);
+				const worktreeName = resolvedPath?.split("/").pop() ?? worktree.branch;
 				const branchName = worktree.branch;
 
</file context>
Fix with Cubic

isUnread={isUnread}
workspaceStatus={workspaceStatus}
sections={sections}
onRecoverWorktree={() => void recoverTrackedWorktree()}
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

P2: Handle errors from recoverTrackedWorktree() instead of discarding its promise with void; otherwise a failed directory-selection mutation can become an unhandled rejection.

(Based on your team's feedback about awaiting/catching async calls to avoid unhandled promise rejections.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx, line 487:

<comment>Handle errors from `recoverTrackedWorktree()` instead of discarding its promise with `void`; otherwise a failed directory-selection mutation can become an unhandled rejection.

(Based on your team's feedback about awaiting/catching async calls to avoid unhandled promise rejections.) </comment>

<file context>
@@ -440,9 +480,11 @@ export function WorkspaceListItem({
 				isUnread={isUnread}
 				workspaceStatus={workspaceStatus}
 				sections={sections}
+				onRecoverWorktree={() => void recoverTrackedWorktree()}
 				onRename={rename.startRename}
 				onOpenInFinder={handleOpenInFinder}
</file context>
Suggested change
onRecoverWorktree={() => void recoverTrackedWorktree()}
onRecoverWorktree={() => {
void recoverTrackedWorktree().catch((error) => {
toast.error(
`Failed to recover worktree: ${error instanceof Error ? error.message : String(error)}`,
);
});
}}
Fix with Cubic

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.

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)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)

128-140: ⚠️ Potential issue | 🟠 Major

showRecoveryView only blocks the layout, not the stale-path side effects.

The new branch still leaves path-dependent hooks and hotkeys mounted above it (useWorkspaceFileEventBridge, useWorkspaceRenameReconciliation, RUN_WORKSPACE_COMMAND, OPEN_IN_APP, COPY_PATH), all keyed off workspace.worktreePath. In repair_required, that is exactly the stale path the user is being asked to fix, so the fullscreen recovery state can still trigger broken operations. Gate those effects/callbacks behind a single canUseWorktreePath flag while recovery is pending.

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

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/`$workspaceId/page.tsx
around lines 128 - 140, The recovery fullscreen (showRecoveryView) blocks
rendering but not path-dependent effects, so create a boolean canUseWorktreePath
(false when workspace?.type === "worktree" && workspace.repairState ===
"repair_required") and use it to gate mounting/activation of all
worktree-path-dependent hooks and callbacks: wrap calls to
useWorkspaceFileEventBridge and useWorkspaceRenameReconciliation and disable
handlers that register RUN_WORKSPACE_COMMAND, OPEN_IN_APP, COPY_PATH when
canUseWorktreePath is false; ensure showInitView logic
(isInitializing/hasFailed/hasIncompleteInit) remains unchanged but these
side-effects do not run while repair is pending.
🧹 Nitpick comments (1)
apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts (1)

258-285: Avoid per-workspace Git scans in getAllGrouped.

resolveWorkspacePathState() can shell out through getBranchWorktreePath() for every unresolved tracked worktree. Doing that inside Promise.all(allWorkspaces.map(...)) means one sidebar refresh can fan out into N git worktree list scans for the same project. Caching by mainRepoPath or precomputing the repo's worktree map once would keep recovery-state calculation off the hot path.

🤖 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/query.ts` around
lines 258 - 285, The current loop calls resolveWorkspacePathState for every
workspace which can invoke getBranchWorktreePath and trigger N git worktree
scans; instead, before Promise.all over allWorkspaces (in getAllGrouped), group
workspaces by their mainRepoPath (use
groupsMap.get(workspace.projectId).project.mainRepoPath), compute a single
worktree map per mainRepoPath (via a new helper like getWorktreeMapForRepo or by
calling getBranchWorktreePath once and building a map), then pass that cached
map into resolveWorkspacePathState (or add an overload
resolveWorkspacePathState(workspace, worktreeMap)) so the per-workspace mapping
uses the precomputed map rather than shelling out repeatedly. Ensure you
reference resolveWorkspacePathState, getBranchWorktreePath, allWorkspaces and
groupsMap when modifying the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts`:
- Around line 825-839: repairWorktreeRegistration currently rethrows the raw
simple-git error; change it to normalize failures into a TRPCError so they match
the surrounding recovery flow. Inside repairWorktreeRegistration (and keep the
same getSimpleGitWithShellPath / git.raw call), catch errors and throw a new
TRPCError (imported from '@trpc/server') with an appropriate code (e.g.,
'BAD_REQUEST' or 'NOT_FOUND') and a clear message that includes the original
error.message to preserve details; do not rethrow the raw error so callers like
repairTrackedWorktreePath receive the structured TRPCError shape.

In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/repair-worktree-path.ts`:
- Around line 144-177: The current code treats any non-persist path as
"git_repair_required" even when getBranchWorktreePath() failed to probe Git or
when Git reports the branch is registered at the main repo; change the control
flow to (1) track probe failure by introducing a probeFailed boolean set true
inside the catch for getBranchWorktreePath, (2) only return git_repair_required
and call persistResolvedTrackedWorktreePath when registeredPath is non-null,
existsSync(registeredPath) is true and isMainRepoPath(context, registeredPath)
is false (keep existing check), (3) if probeFailed is true return a resolution
with status "git_probe_failed" (including branch, mainRepoPath, storedPath and
registeredPath=null), and (4) if registeredPath is non-null but
isMainRepoPath(context, registeredPath) is true treat it as not
registered/missing by returning a resolution with status "git_not_registered"
(include branch, mainRepoPath, registeredPath and storedPath). Use the existing
functions getBranchWorktreePath, isMainRepoPath, and
persistResolvedTrackedWorktreePath and the context.worktree fields to implement
these branches.

In
`@apps/desktop/src/renderer/react-query/workspaces/useRecoverTrackedWorktree.ts`:
- Around line 14-15: The directory picker call selectDirectory.mutateAsync() is
executed outside the try/catch in recoverTrackedWorktree, so picker/IPC errors
become unhandled rejections; move or wrap the selectDirectory.mutateAsync() call
inside the existing try/catch (or add a dedicated try/catch around it) in the
recoverTrackedWorktree flow so any rejection is caught, call the same
toast/error handling used for repairTrackedWorktree failures, and ensure the
function returns early on picker failure to avoid continuing with invalid data.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceContextMenu.tsx`:
- Around line 204-212: When isRepairRequired is true we must not render the
path-based actions from commonContextMenuItems next to the Recover worktree
entry because they operate on a stale path; update WorkspaceContextMenu so that
when isRepairRequired is true it either filters out or disables the path-related
items from commonContextMenuItems (the Open in Finder / Copy Path handlers)
instead of rendering them normally, and ensure onRecoverWorktree updates the
repair state so the items are restored when repair completes; specifically
locate the usage of commonContextMenuItems in WorkspaceContextMenu and change
rendering to conditional rendering or pass a prop/flag to those menu items to
render disabled/no-op handlers while isRepairRequired is true.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/`$workspaceId/page.tsx:
- Around line 128-140: The recovery fullscreen (showRecoveryView) blocks
rendering but not path-dependent effects, so create a boolean canUseWorktreePath
(false when workspace?.type === "worktree" && workspace.repairState ===
"repair_required") and use it to gate mounting/activation of all
worktree-path-dependent hooks and callbacks: wrap calls to
useWorkspaceFileEventBridge and useWorkspaceRenameReconciliation and disable
handlers that register RUN_WORKSPACE_COMMAND, OPEN_IN_APP, COPY_PATH when
canUseWorktreePath is false; ensure showInitView logic
(isInitializing/hasFailed/hasIncompleteInit) remains unchanged but these
side-effects do not run while repair is pending.

---

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts`:
- Around line 258-285: The current loop calls resolveWorkspacePathState for
every workspace which can invoke getBranchWorktreePath and trigger N git
worktree scans; instead, before Promise.all over allWorkspaces (in
getAllGrouped), group workspaces by their mainRepoPath (use
groupsMap.get(workspace.projectId).project.mainRepoPath), compute a single
worktree map per mainRepoPath (via a new helper like getWorktreeMapForRepo or by
calling getBranchWorktreePath once and building a map), then pass that cached
map into resolveWorkspacePathState (or add an overload
resolveWorkspacePathState(workspace, worktreeMap)) so the per-workspace mapping
uses the precomputed map rather than shelling out repeatedly. Ensure you
reference resolveWorkspacePathState, getBranchWorktreePath, allWorkspaces and
groupsMap when modifying the logic.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2916f6ea-96d5-40ec-acdc-15f5379b500f

📥 Commits

Reviewing files that changed from the base of the PR and between 6837adc and d51a007.

📒 Files selected for processing (14)
  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/repair.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/repair-worktree-path.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/renderer/react-query/workspaces/index.ts
  • apps/desktop/src/renderer/react-query/workspaces/useRecoverTrackedWorktree.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceList/WorkspaceList.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/types.ts

Comment on lines +825 to +839
export async function repairWorktreeRegistration({
mainRepoPath,
worktreePath,
}: {
mainRepoPath: string;
worktreePath: string;
}): Promise<void> {
try {
const git = await getSimpleGitWithShellPath(mainRepoPath);
await git.raw(["worktree", "repair", worktreePath]);
} catch (error) {
console.error(`Failed to repair worktree registration: ${error}`);
throw error;
}
}
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.

⚠️ Potential issue | 🟠 Major

Normalize git worktree repair failures before they escape the recovery flow.

repairTrackedWorktreePath() calls this helper without its own catch, so rethrowing the raw simple-git error here makes this step the only one in the flow that skips the structured TRPCError mapping used for invalid folders, wrong branches, and missing paths. A bad selection at this point will fall through as an untyped backend failure instead of the same actionable recovery error shape as the surrounding checks.

🤖 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/utils/git.ts` around lines 825 -
839, repairWorktreeRegistration currently rethrows the raw simple-git error;
change it to normalize failures into a TRPCError so they match the surrounding
recovery flow. Inside repairWorktreeRegistration (and keep the same
getSimpleGitWithShellPath / git.raw call), catch errors and throw a new
TRPCError (imported from '@trpc/server') with an appropriate code (e.g.,
'BAD_REQUEST' or 'NOT_FOUND') and a clear message that includes the original
error.message to preserve details; do not rethrow the raw error so callers like
repairTrackedWorktreePath receive the structured TRPCError shape.

Comment on lines +144 to +177
let registeredPath: string | null = null;
try {
registeredPath = await getBranchWorktreePath({
mainRepoPath: context.mainRepoPath,
branch: context.worktree.branch,
});
} catch (error) {
console.warn(
`[repair-worktree-path] Failed to inspect Git worktree state for ${context.worktree.id}:`,
error instanceof Error ? error.message : error,
);
}

if (
registeredPath &&
existsSync(registeredPath) &&
!isMainRepoPath(context, registeredPath)
) {
return persistResolvedTrackedWorktreePath({
context,
resolvedPath: registeredPath,
});
}

return {
pathChanged: false,
resolution: {
status: "git_repair_required",
branch: context.worktree.branch,
mainRepoPath: context.mainRepoPath,
registeredPath,
storedPath: context.worktree.path,
},
};
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.

⚠️ Potential issue | 🟠 Major

Differentiate probe failures from genuine repairable moves.

This block falls back to git_repair_required even when getBranchWorktreePath() could not inspect Git at all, or when Git only reports mainRepoPath for the branch. In both cases the sidebar/page recovery flow and resolveWorktreePathOrThrow* callers will tell the user to repair a moved worktree that Git never actually identified. Only emit the repair state when Git positively points at a non-main worktree registration; otherwise surface the probe failure or treat it as missing.

🤖 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/utils/repair-worktree-path.ts`
around lines 144 - 177, The current code treats any non-persist path as
"git_repair_required" even when getBranchWorktreePath() failed to probe Git or
when Git reports the branch is registered at the main repo; change the control
flow to (1) track probe failure by introducing a probeFailed boolean set true
inside the catch for getBranchWorktreePath, (2) only return git_repair_required
and call persistResolvedTrackedWorktreePath when registeredPath is non-null,
existsSync(registeredPath) is true and isMainRepoPath(context, registeredPath)
is false (keep existing check), (3) if probeFailed is true return a resolution
with status "git_probe_failed" (including branch, mainRepoPath, storedPath and
registeredPath=null), and (4) if registeredPath is non-null but
isMainRepoPath(context, registeredPath) is true treat it as not
registered/missing by returning a resolution with status "git_not_registered"
(include branch, mainRepoPath, registeredPath and storedPath). Use the existing
functions getBranchWorktreePath, isMainRepoPath, and
persistResolvedTrackedWorktreePath and the context.worktree fields to implement
these branches.

Comment on lines +14 to +15
const selectDirectory = electronTrpc.window.selectDirectory.useMutation();
const repairTrackedWorktree =
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.

⚠️ Potential issue | 🟡 Minor

Handle directory-picker failures too.

selectDirectory.mutateAsync() sits outside the try/catch. Since callers invoke recoverTrackedWorktree() with void, any picker/IPC failure becomes an unhandled rejection and no toast is shown.

Suggested fix
-	const selectDirectory = electronTrpc.window.selectDirectory.useMutation();
+	const selectDirectory = electronTrpc.window.selectDirectory.useMutation({
+		onError: (error) => {
+			toast.error(`Failed to choose folder: ${error.message}`);
+		},
+	});

 	const recoverTrackedWorktree = async () => {
-		const result = await selectDirectory.mutateAsync({
-			title: "Select moved worktree folder",
-			defaultPath: defaultPath ?? undefined,
-		});
-		if (result.canceled || !result.path) {
-			return;
-		}
-
 		try {
+			const result = await selectDirectory.mutateAsync({
+				title: "Select moved worktree folder",
+				defaultPath: defaultPath ?? undefined,
+			});
+			if (result.canceled || !result.path) {
+				return;
+			}
+
 			await repairTrackedWorktree.mutateAsync({
 				workspaceId,
 				selectedPath: result.path,
 			});
 		} catch {
-			// Mutation onError already surfaces the failure to the user.
+			// Mutation onError handlers already surface failures to the user.
 		}
 	};

Also applies to: 29-45

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

In
`@apps/desktop/src/renderer/react-query/workspaces/useRecoverTrackedWorktree.ts`
around lines 14 - 15, The directory picker call selectDirectory.mutateAsync() is
executed outside the try/catch in recoverTrackedWorktree, so picker/IPC errors
become unhandled rejections; move or wrap the selectDirectory.mutateAsync() call
inside the existing try/catch (or add a dedicated try/catch around it) in the
recoverTrackedWorktree flow so any rejection is caught, call the same
toast/error handling used for repairTrackedWorktree failures, and ensure the
function returns early on picker failure to avoid continuing with invalid data.

Comment on lines +204 to +212
{isRepairRequired && (
<>
<ContextMenuItem onSelect={onRecoverWorktree}>
<LuWrench className="size-4 mr-2" strokeWidth={STROKE_WIDTH} />
Recover worktree
</ContextMenuItem>
<ContextMenuSeparator />
</>
)}
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.

⚠️ Potential issue | 🟠 Major

Don’t keep stale-path actions next to “Recover worktree”.

When isRepairRequired is true, this adds the recovery entry but still renders Open in Finder and Copy Path from commonContextMenuItems immediately below. Those handlers still target the stale tracked path that triggered recovery, so the menu exposes known-broken actions in the very state that is supposed to steer the user toward repair. Hide or disable the path-based entries until recovery succeeds.

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

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceContextMenu.tsx`
around lines 204 - 212, When isRepairRequired is true we must not render the
path-based actions from commonContextMenuItems next to the Recover worktree
entry because they operate on a stale path; update WorkspaceContextMenu so that
when isRepairRequired is true it either filters out or disables the path-related
items from commonContextMenuItems (the Open in Finder / Copy Path handlers)
instead of rendering them normally, and ensure onRecoverWorktree updates the
repair state so the items are restored when repair completes; specifically
locate the usage of commonContextMenuItems in WorkspaceContextMenu and change
rendering to conditional rendering or pass a prop/flag to those menu items to
render disabled/no-op handlers while isRepairRequired is true.

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