Skip to content

Make worktree deletion tolerant of part-way failures and offer retry - #32

Merged
seankearon merged 1 commit into
mainfrom
claude/branch-worktree-deletion-reporting-znuq2j
Aug 6, 2026
Merged

Make worktree deletion tolerant of part-way failures and offer retry#32
seankearon merged 1 commit into
mainfrom
claude/branch-worktree-deletion-reporting-znuq2j

Conversation

@seankearon

Copy link
Copy Markdown
Owner

Summary

Refactors worktree deletion to be tolerant of partial failures and provide users with a retry mechanism for outstanding steps. Instead of abandoning the entire operation when one step fails, the three deletion targets (worktree folder, local branch, remote branch) are now independent and each failure is reported separately. Targets that were already gone (e.g., a branch deleted on the server) are now correctly reported as success rather than failure.

Key Changes

  • New deletion outcome model: Introduced WorktreeDeletionOutcome and WorktreeDeletionStep to track each deletion target's status independently, replacing the previous boolean-based outcome. Each step records whether it was deleted, already gone, failed, or skipped.

  • Failure tolerance: Modified OpenerService.DeleteWorktreeAsync() and ForceDeleteWorktreeAsync() to continue attempting remaining targets even when one fails, collecting all results in the outcome rather than throwing immediately (except for worktree removal, which still throws to offer the force-delete fallback).

  • "Already gone" detection: Added GitAlreadyGone service to distinguish between genuine failures and cases where the target was already removed (e.g., "remote ref does not exist", "branch not found"). These are reported as DeletionStepStatus.AlreadyGone and count as success.

  • Accurate reporting: Introduced DeletionReport service to generate flight-log summaries and retry strip prompts that accurately reflect what was removed, what failed, and what was already gone. Uses ✓ for success (including already-gone targets) and ⚠ only for targets still standing.

  • Retry mechanism: Added retry strip UI and logic in MainWindow to re-run only the outstanding deletion steps. The RetryDeleteAsync() method merges previous outcomes with new attempts so the final report covers the whole operation.

  • UI updates: Added delete retry strip in XAML with headline and detail text, and corresponding ViewModel properties (IsDeleteRetryPending, DeleteRetryHeadline, DeleteRetryDetail) to control visibility and content.

Notable Implementation Details

  • Outcomes are merged across attempts using WorktreeDeletionOutcome.Merge() so a successful retry reports the complete picture (e.g., "✓ Removed worktree & branch 'feature/x' + origin/feature/x" after a retry succeeds).
  • The retry strip outlives the deleted card by sitting outside the delete row, so it remains visible even after the results list empties.
  • Worktree removal failures still throw WorktreeRemovalException to trigger the disk-level force-delete fallback, but other failures are collected in the outcome for retry.
  • All three deletion targets are attempted regardless of earlier failures, enabling accurate reporting of what succeeded and what didn't.

https://claude.ai/code/session_015S6Vfx6cChWYBdnmnV1pzH

Deleting a worktree whose branch had already gone from origin reported a
failure. The flight log showed the red `[!] Remote branch origin/<b>
could not be deleted: ... remote ref does not exist` followed by `⚠
Removed worktree & branch '<b>', but origin/<b> could not be deleted —
see log.`, though the worktree and the local branch had been removed
cleanly and origin no longer had the branch either. Everything the user
asked for had happened; only the report said otherwise.

The three targets a delete removes — the worktree, the local branch, the
branch on origin — are now attempted and reported on separately. Each
step returns a WorktreeDeletionStep (Deleted / AlreadyGone / Failed /
Skipped) rather than a bool, so:

- A target that had already gone counts as success. GitAlreadyGone
  recognises git's own wording for "there was nothing to delete"
  ("remote ref does not exist", "branch 'x' not found", "is not a
  working tree"); a stale worktree registration is pruned so the branch
  is still free to delete. Deliberately narrow — anything unlisted stays
  a failure, which is the safe way round.
- A failed step no longer abandons the ones after it. A local branch
  delete that fails used to throw and take the origin delete with it;
  both now run and report independently.
- DeletionReport composes the closing line from the steps, so a ⚠ is
  spent only on something asked for that is genuinely still there.

Whatever is still standing is offered back as an inline Retry strip:
what's left, git's words for why, and Retry / Dismiss. Retrying re-runs
only the outstanding steps (Outstanding(choice) over the merged
outcome), and the report that follows covers the whole attempt rather
than the last pass. The strip sits outside the delete row so it outlives
the card just deleted; Esc or Dismiss drops it, and a fresh scan clears
it as stale. Declining the force-delete fallback now leaves the delete
on offer instead of losing it.

The one throw kept is a genuinely failed worktree removal — the caller
still needs it to offer the Recycle-Bin-bypassing folder delete.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015S6Vfx6cChWYBdnmnV1pzH
@seankearon
seankearon merged commit 7dfd58c into main Aug 6, 2026
0 of 4 checks passed
@seankearon
seankearon deleted the claude/branch-worktree-deletion-reporting-znuq2j branch August 6, 2026 17:13
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.

2 participants