Skip to content

fix(worktree): allow force-closing orphaned worktrees#158

Draft
tomasfejfar wants to merge 1 commit into
contember:mainfrom
tomasfejfar:fix-orphaned-worktree-close
Draft

fix(worktree): allow force-closing orphaned worktrees#158
tomasfejfar wants to merge 1 commit into
contember:mainfrom
tomasfejfar:fix-orphaned-worktree-close

Conversation

@tomasfejfar

Copy link
Copy Markdown
Contributor

Problem

A worktree shown in the sidebar could get into a state where "Close Worktree" always failed and there was no way to remove it from the app:

Failed to remove worktree: git exited with status 128:
fatal: not a git repository: …/.git/worktrees/<name>

Root cause

When a worktree's metadata entry (<main-repo>/.git/worktrees/<name>) is pruned — e.g. git worktree prune runs while removing a different worktree (prune wipes every stale entry, not just the one being removed) — the checkout directory survives on disk but its .git pointer now dangles. Git no longer recognizes it as a worktree, so git worktree remove exits 128.

okena's existing auto-cleanup (worktree_sync.rsremove_stale_worktree) only fires when the directory no longer exists. An orphaned-but-present directory is never cleaned up, so the entry stays stuck in the sidebar with no working removal path.

Changes

A two-step UX: the normal close runs exactly as before. Only after the final removal fails does the dialog reveal a destructive "Delete folder anyway" button.

  • okena-gitresolve_worktree_root_fs: resolves a worktree checkout root by walking up to the nearest .git entry, without opening the repo via gix (which fails on a dangling pointer). Handles monorepo subdir projects too.
  • okena-workspaceWorkspace::force_remove_worktree_project: deletes the on-disk directory and prunes stale metadata (reusing the existing remove_worktree_fast), then de-registers the project. It deliberately bypasses git's dirty-state safety because git can no longer run that check on a worktree it doesn't track — hence the second click is required to reach it.
  • okena-views-git (Close Worktree dialog) — a show_force_remove flag set only on the final removal failure (not on stash/fetch/rebase/merge failures, which force-delete can't fix), an async force_remove(), and the destructive footer button rendered only when that flag is set.

No public behavior changes for healthy worktrees; the new button never appears unless a removal has already failed.

How to verify it

  • cargo test -p okena-git — new force_removes_orphaned_worktree test reproduces the exact status-128 failure and proves the fallback resolves the root and cleans up (119 passed)
  • cargo test -p okena-workspace — no regressions (294 passed)
  • cargo build -p okena-git -p okena-workspace -p okena-views-git — all changed crates compile
  • Manual (needs a human): on a real orphaned worktree, open Close Worktree → confirm the normal action still fails with the same error AND the new "Delete folder anyway" button now appears → click it → the entry leaves the sidebar and the directory is removed from disk.
  • Manual (regression): close a healthy worktree the normal way → the force button must NOT appear and behavior is unchanged.

Note: the full desktop binary build additionally requires the web client (web/dist, built via yarn build in web/) for the unrelated okena-remote-server crate; that is a pre-existing prerequisite, not part of this change.

Changelog

Fixed: orphaned worktrees (directory present but no longer tracked by git) can now be removed from the sidebar via a "Delete folder anyway" fallback that appears after a failed close.

When a worktree's metadata entry is pruned from the main repo (e.g. by
`git worktree prune` while removing another worktree), its checkout
survives on disk with a dangling `.git` pointer. `git worktree remove`
then exits 128 ("not a git repository"), and okena's only auto-cleanup
fires when the directory is *gone* — so an orphaned-but-present worktree
stays stuck in the sidebar with no working removal path.

Add a destructive "Delete folder anyway" fallback that appears only
after the normal close fails: it resolves the checkout root without git
(walking up to the `.git` pointer), deletes the directory, prunes stale
metadata, and de-registers the project.
@tomasfejfar tomasfejfar force-pushed the fix-orphaned-worktree-close branch from ce95fb7 to 691f370 Compare July 1, 2026 13:01
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