Skip to content

fix(host-service): tolerate locked+missing worktrees in destroy#4693

Merged
Kitenite merged 1 commit into
mainfrom
fix-missing-worktree-dele
May 19, 2026
Merged

fix(host-service): tolerate locked+missing worktrees in destroy#4693
Kitenite merged 1 commit into
mainfrom
fix-missing-worktree-dele

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented May 18, 2026

Summary

  • Customers can end up with a workspace whose worktree directory is gone from disk. If git also has the worktree locked (rare but possible — a long-running operation or tool that locks during work, then crashes), git worktree remove --force fails with fatal: cannot remove a locked working tree; use 'remove -f -f' to override or unlock first. The existing substring matcher in workspace-cleanup.ts:294-300 doesn't catch this phrasing, so the step pushes a confusing "Failed to remove worktree" warning even though the directory the user wanted gone is already gone.
  • Escalate the command to --force --force (we're past the cloud-delete commit point — the workspace is already gone from the user's perspective, so the conservative single --force no longer earns its keep) and add an existsSync fallback: any git failure whose post-condition is "the worktree dir is gone" is success, regardless of what git printed. The substring matcher stays as belt-and-braces for the rare race where the dir disappears mid-remove.
  • Add an integration test for the locked-and-missing case. Verified it fails on the unfixed code (worktreeRemoved: false, warnings non-empty) and passes after the fix.

Closes SUPER-779.

Test plan

  • bun test packages/host-service/test/integration/workspace-cleanup.integration.test.ts — 10 pass (9 existing + 1 new).
  • bun run lint clean.
  • bunx tsc --noEmit clean.
  • Manual: in desktop v2, create a workspace, rm -rf its worktree dir, click delete from the sidebar — workspace disappears with no warning toasts.
  • Manual locked variant: same but also git worktree lock <path> first.

Out of scope

The renderer-side paths (useDestroyWorkspace.normalizeError, dialog inspect-failure handling) were verified to already swallow git failures correctly — no changes needed there.


Open in Stage

Summary by cubic

Makes host-service workspace destroy tolerate locked + missing git worktrees so deletes complete without false warnings. Addresses SUPER-779 by escalating remove and treating a missing worktree dir as success.

  • Bug Fixes
    • Use git worktree remove --force --force to handle locked worktrees after manual rm -rf.
    • If existsSync(worktreePath) is false after a git error, mark the worktree as removed; keep the substring matcher as a fallback.
    • Added an integration test for the locked+missing case to ensure clean deletes with no warnings.

Written for commit 2043ca0. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved workspace cleanup reliability for edge cases involving locked worktrees.
    • Enhanced error handling to properly detect when worktree directories have been manually removed, ensuring cleanup operations complete successfully.
    • Added integration test coverage for locked worktree scenarios with manually deleted directories.

Review Change Stack

…ktrees

`git worktree remove --force` fails with "cannot remove a locked working
tree" when a workspace's worktree is locked and its directory has been
manually deleted — the existing substring matcher didn't catch this and
the step pushed a confusing "Failed to remove worktree" warning even
though the dir was already gone.

Escalate to `--force --force` (we're past the commit point) and add an
`existsSync` fallback: if the worktree dir is gone, the user's intent is
satisfied regardless of what git printed (locked, locale-translated,
future phrasing). The substring matcher stays as defense for the rare
race where the dir disappears mid-remove.

Closes SUPER-779.
@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 18, 2026

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@stage-review
Copy link
Copy Markdown

stage-review Bot commented May 18, 2026

Ready to review this PR? Stage has broken it down into 2 individual chapters for you:

Title
1 Fix worktree removal for locked and missing directories
2 Add integration test for locked-and-missing worktree scenario
Open in Stage

Chapters generated by Stage for commit 2043ca0 on May 18, 2026 10:16pm UTC.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 662f2d3c-efb9-4f76-8f73-6e6e4d0a8b56

📥 Commits

Reviewing files that changed from the base of the PR and between b4859a3 and 2043ca0.

📒 Files selected for processing (2)
  • packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
  • packages/host-service/test/integration/workspace-cleanup.integration.test.ts

📝 Walkthrough

Walkthrough

The workspace cleanup flow now reliably handles locked worktrees by importing existsSync to check disk state, doubling the --force flag in the git worktree remove command, and falling back to filesystem existence checks before interpreting git error messages. A new integration test validates the improved error handling when a worktree is locked and its directory manually deleted.

Changes

Workspace Cleanup Resilience

Layer / File(s) Summary
Worktree removal with existsSync fallback
packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
existsSync is imported and used as an authoritative fallback check when worktree removal fails. The git worktree remove command now passes --force twice to handle locked-worktree edge cases, with an updated comment documenting this behavior. Error handling now prioritizes filesystem existence checks over git error message parsing.
Integration test for locked worktree cleanup
packages/host-service/test/integration/workspace-cleanup.integration.test.ts
New test case locks a worktree, deletes its directory, and verifies that workspaceCleanup.destroy completes successfully without warnings, removing both the worktree entry and the associated branch.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A worktree once locked and then gone,
Now checked by the disk before moving on.
With force flags doubled and paths verified true,
The cleanup runs smooth when the files bid adieu! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: tolerating locked and missing worktrees in the destroy operation. It accurately reflects the core change in the changeset.
Description check ✅ Passed The description includes all required template sections: comprehensive Summary, Related Issues link (closes SUPER-779), Type of Change (bug fix), Testing details with test results, and Additional Notes explaining out-of-scope considerations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ 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-missing-worktree-dele

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 18, 2026

Greptile Summary

This PR fixes a case where git worktree remove --force fails on locked worktrees whose directories have already been deleted, generating a spurious warning even though the user's goal is met. The fix escalates to --force --force (which overrides locks) and adds an existsSync-based fallback in the catch block so any git failure where the worktree directory is already absent is treated as success.

  • Upgrades the git worktree remove call in the destroy path from single to double --force, removing the locked-worktree failure mode entirely for the common case.
  • Adds an existsSync(local.worktreePath) check in the error handler as a locale/phrasing-agnostic safety net, falling through to the existing substring matchers only when the directory still exists.
  • Adds an integration test covering the locked+missing scenario; the test verifies worktreeRemoved: true, empty warnings, and that git's own worktree list no longer shows the path.

Confidence Score: 4/5

Safe to merge; the destroy path is already a best-effort, warnings-only operation past the cloud-delete commit point, so the more aggressive double-force removal is appropriate.

The two-line production change is narrow and well-targeted. The existsSync fallback path — where git fails even with --force --force but the directory is already gone — is not exercised by the new test, meaning git internal worktree metadata could theoretically be left stale while worktreeRemoved reports true. That gap is very unlikely in practice.

The test comment in workspace-cleanup.integration.test.ts mildly overstates the role of the existsSync fallback; the production file itself is clean.

Important Files Changed

Filename Overview
packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts Escalates worktree removal to --force --force and adds existsSync fallback in catch; change is targeted and well-commented, with one minor gap where the fallback path could silently leave stale git-internal metadata if git fails even with double-force.
packages/host-service/test/integration/workspace-cleanup.integration.test.ts New test correctly sets up the locked+missing scenario and asserts clean deletion with no warnings; the test exercises the --force --force success path (not the existsSync fallback), which the in-code comment slightly misstates.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[destroy called] --> B[Open git repo]
    B -->|error| C[push warning: Failed to open repo]
    B -->|success| D["git worktree remove --force --force"]
    D -->|success| E[worktreeRemoved = true]
    D -->|throws| F{existsSync worktreePath?}
    F -->|false: dir already gone| G[worktreeRemoved = true, no warning]
    F -->|true: dir still present| H{substring match?}
    H -->|matches known message| I[worktreeRemoved = true, no warning]
    H -->|no match| J[push warning: Failed to remove worktree]
    E --> K[optional branch delete]
    G --> K
    I --> K
    J --> K
    K --> L[delete SQLite row]
    L --> M[return result]
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/host-service/test/integration/workspace-cleanup.integration.test.ts:196-200
The test comment implies that the `existsSync` fallback is what resolves the locked+missing case, but with `--force --force` in place git actually succeeds and removes its internal metadata cleanly — so the test exercises the success branch, not the fallback. The fallback (and the scenario where git's `.git/worktrees/` metadata might be left stale) goes untested. Worth a small wording tweak so future readers don't misread which branch they're actually covering.

```suggestion
		// A locked worktree whose dir was manually deleted is the scenario
		// that breaks the substring-based error matcher: git says
		// "fatal: cannot remove a locked working tree" and single `--force`
		// is not enough. `--force --force` overrides the lock and removes
		// git's internal metadata cleanly (the success path). The existsSync
		// fallback in the catch block is an additional safety net for any
		// future git failure where the dir is already gone but git still
		// throws; that path is not exercised here.
```

Reviews (1): Last reviewed commit: "fix(host-service): make workspace destro..." | Re-trigger Greptile

Comment on lines +196 to +200
// A locked worktree whose dir was manually deleted is the scenario
// that breaks the substring-based error matcher: git says
// "fatal: cannot remove a locked working tree" and single `--force`
// is not enough. `--force --force` plus the existsSync fallback
// closes the loop so the user always gets a clean delete.
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 The test comment implies that the existsSync fallback is what resolves the locked+missing case, but with --force --force in place git actually succeeds and removes its internal metadata cleanly — so the test exercises the success branch, not the fallback. The fallback (and the scenario where git's .git/worktrees/ metadata might be left stale) goes untested. Worth a small wording tweak so future readers don't misread which branch they're actually covering.

Suggested change
// A locked worktree whose dir was manually deleted is the scenario
// that breaks the substring-based error matcher: git says
// "fatal: cannot remove a locked working tree" and single `--force`
// is not enough. `--force --force` plus the existsSync fallback
// closes the loop so the user always gets a clean delete.
// A locked worktree whose dir was manually deleted is the scenario
// that breaks the substring-based error matcher: git says
// "fatal: cannot remove a locked working tree" and single `--force`
// is not enough. `--force --force` overrides the lock and removes
// git's internal metadata cleanly (the success path). The existsSync
// fallback in the catch block is an additional safety net for any
// future git failure where the dir is already gone but git still
// throws; that path is not exercised here.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/test/integration/workspace-cleanup.integration.test.ts
Line: 196-200

Comment:
The test comment implies that the `existsSync` fallback is what resolves the locked+missing case, but with `--force --force` in place git actually succeeds and removes its internal metadata cleanly — so the test exercises the success branch, not the fallback. The fallback (and the scenario where git's `.git/worktrees/` metadata might be left stale) goes untested. Worth a small wording tweak so future readers don't misread which branch they're actually covering.

```suggestion
		// A locked worktree whose dir was manually deleted is the scenario
		// that breaks the substring-based error matcher: git says
		// "fatal: cannot remove a locked working tree" and single `--force`
		// is not enough. `--force --force` overrides the lock and removes
		// git's internal metadata cleanly (the success path). The existsSync
		// fallback in the catch block is an additional safety net for any
		// future git failure where the dir is already gone but git still
		// throws; that path is not exercised here.
```

How can I resolve this? If you propose a fix, please make it concise.

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

Re-trigger cubic

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

@Kitenite Kitenite merged commit b33c254 into main May 19, 2026
17 checks passed
@Kitenite Kitenite deleted the fix-missing-worktree-dele branch May 19, 2026 04:07
sazabi Bot pushed a commit that referenced this pull request May 20, 2026
…ktrees (#4693)

`git worktree remove --force` fails with "cannot remove a locked working
tree" when a workspace's worktree is locked and its directory has been
manually deleted — the existing substring matcher didn't catch this and
the step pushed a confusing "Failed to remove worktree" warning even
though the dir was already gone.

Escalate to `--force --force` (we're past the commit point) and add an
`existsSync` fallback: if the worktree dir is gone, the user's intent is
satisfied regardless of what git printed (locked, locale-translated,
future phrasing). The substring matcher stays as defense for the rare
race where the dir disappears mid-remove.

Closes SUPER-779.
MocA-Love pushed a commit to MocA-Love/superset that referenced this pull request May 25, 2026
…ktrees (superset-sh#4693)

`git worktree remove --force` fails with "cannot remove a locked working
tree" when a workspace's worktree is locked and its directory has been
manually deleted — the existing substring matcher didn't catch this and
the step pushed a confusing "Failed to remove worktree" warning even
though the dir was already gone.

Escalate to `--force --force` (we're past the commit point) and add an
`existsSync` fallback: if the worktree dir is gone, the user's intent is
satisfied regardless of what git printed (locked, locale-translated,
future phrasing). The substring matcher stays as defense for the rare
race where the dir disappears mid-remove.

Closes SUPER-779.
MocA-Love added a commit to MocA-Love/superset that referenced this pull request May 25, 2026
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