Make worktree deletion tolerant of part-way failures and offer retry - #32
Merged
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
WorktreeDeletionOutcomeandWorktreeDeletionStepto 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()andForceDeleteWorktreeAsync()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
GitAlreadyGoneservice 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 asDeletionStepStatus.AlreadyGoneand count as success.Accurate reporting: Introduced
DeletionReportservice 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
MainWindowto re-run only the outstanding deletion steps. TheRetryDeleteAsync()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
WorktreeDeletionOutcome.Merge()so a successful retry reports the complete picture (e.g., "✓ Removed worktree & branch 'feature/x' + origin/feature/x" after a retry succeeds).WorktreeRemovalExceptionto trigger the disk-level force-delete fallback, but other failures are collected in the outcome for retry.https://claude.ai/code/session_015S6Vfx6cChWYBdnmnV1pzH