Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions docs/superpowers/plans/2026-03-22-dirty-worktree-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Dirty Worktree Handling Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Fix `done-feature` so it gracefully handles dirty worktrees instead of dying under `set -e` and leaving partial state.

**Architecture:** Replace the bare `git worktree remove` on line 31 with an `if/else` block that mirrors the existing branch-deletion pattern (lines 35–45). Clean worktrees proceed silently; dirty ones show uncommitted files and prompt for confirmation before force-removing.

**Tech Stack:** Bash, git

---

## Files

- Modify: `done-feature:31-32`

---

### Task 1: Replace bare `git worktree remove` with guarded if/else block

**Files:**
- Modify: `done-feature:31-32`

- [ ] **Step 1: Verify the current state**

Read lines 29–35 of `done-feature` to confirm the current code matches what the plan expects before editing:

```bash
# ── 4. Remove the worktree ───────────────────────────────────────────────────
git worktree remove "$WORKTREE_DIR"
echo "Removed worktree: $WORKTREE_DIR"
```

- [ ] **Step 2: Replace lines 30–32 with the guarded block**

Replace section 4 of `done-feature` with:

```bash
# ── 4. Remove the worktree ───────────────────────────────────────────────────
if git worktree remove "$WORKTREE_DIR" 2>/dev/null; then
echo "Removed worktree: $WORKTREE_DIR"
else
echo "Worktree has uncommitted changes:"
git -C "$WORKTREE_DIR" status --short
echo
read -rp "Discard changes and force remove? [y/N] " CONFIRM
if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then
git worktree remove --force "$WORKTREE_DIR"
echo "Force removed worktree: $WORKTREE_DIR"
else
echo "Aborted. Worktree kept: $WORKTREE_DIR"
exit 0
fi
fi
```

Key points:
- The `if` condition suppresses `set -e` for the first `git worktree remove` — standard bash behavior
- `2>/dev/null` suppresses git's own error output so our message appears instead
- `--force` on the second call discards tracked and untracked changes
- On abort (`N` or Enter), `exit 0` prevents branch deletion and workspace close from running

- [ ] **Step 3: Manual test — clean worktree (happy path)**

From a real feature worktree with no uncommitted changes, run `done-feature`. Expected:
- No prompt shown
- "Removed worktree: ..." printed
- Continues to branch deletion and workspace close
- `git worktree list` no longer shows the removed worktree

- [ ] **Step 4: Manual test — dirty worktree, confirm discard**

Create a scratch worktree, add an uncommitted file, run `done-feature`, enter `y`. Expected:
- "Worktree has uncommitted changes:" printed
- `git status --short` output shown (e.g., `?? scratch.txt`)
- Prompt appears: "Discard changes and force remove? [y/N]"
- After `y`: "Force removed worktree: ..." printed
- Continues to branch deletion
- `git worktree list` no longer shows the removed worktree

- [ ] **Step 5: Manual test — dirty worktree, abort**

Create a scratch worktree, add an uncommitted file, run `done-feature`, press Enter without typing anything (default No — also verify this explicitly, not just typing `n`). Expected:
- Same status output and prompt as step 4
- After Enter: "Aborted. Worktree kept: ..." printed, script exits
- `git worktree list` still shows the worktree
- `git branch --list $BRANCH` still returns the branch
- Workspace still open
- Shell is now in `$MAIN_ROOT` (the script `cd`'d there before the prompt)

- [ ] **Step 6: Commit**

```bash
git add done-feature
git commit -m "fix: handle dirty worktrees in done-feature with user prompt"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Dirty Worktree Handling in `done-feature`

**Date:** 2026-03-22
**Status:** Approved

---

## Problem

`done-feature` uses `set -e` throughout. When `git worktree remove` is called on a worktree with uncommitted changes, git exits non-zero and `set -e` kills the script immediately — before branch deletion (step 5) or workspace close (step 6) can run. The user is left with:

- the worktree still on disk
- the branch still existing
- the cmux workspace still open

Recovery requires manual intervention (`git worktree remove --force`, `git branch -D`, `cmux close-workspace`).

---

## Design

Replace the bare `git worktree remove` on line 31 of `done-feature` with an `if/else` block, mirroring the existing branch-deletion pattern (lines 35–45).

### Changed section (step 4)

**Before:**
```bash
# ── 4. Remove the worktree ───────────────────────────────────────────────────
git worktree remove "$WORKTREE_DIR"
echo "Removed worktree: $WORKTREE_DIR"
```

**After:**
```bash
# ── 4. Remove the worktree ───────────────────────────────────────────────────
if git worktree remove "$WORKTREE_DIR" 2>/dev/null; then
echo "Removed worktree: $WORKTREE_DIR"
else
echo "Worktree has uncommitted changes:"
git -C "$WORKTREE_DIR" status --short
echo
read -rp "Discard changes and force remove? [y/N] " CONFIRM
if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then
git worktree remove --force "$WORKTREE_DIR"
echo "Force removed worktree: $WORKTREE_DIR"
else
echo "Aborted. Worktree kept: $WORKTREE_DIR"
exit 0
fi
fi
```

### Behavior

| State | Result |
|---|---|
| Clean worktree | Identical to current behavior — no visible change |
| Dirty worktree, user confirms discard | Force removes; continues to branch deletion and workspace close |
| Dirty worktree, user declines | Prints "Aborted", exits 0; worktree, branch, and workspace all left intact. Note: the script has already `cd`'d to `$MAIN_ROOT` (step 3), so the user's shell lands in the main worktree root, not the feature worktree. |

### Key notes

- `set -e` does not apply to commands used as an `if` condition — this is standard bash behavior, no `set +e` needed.
- `2>/dev/null` suppresses git's own error output; the script prints its own message instead.
- `--force` on `git worktree remove` discards tracked and untracked changes without a separate `git clean` call.
- On abort (`exit 0`), execution never reaches branch deletion or workspace close — consistent with the user's intent to keep everything intact.

---

## Scope

Single file: `done-feature`, lines 31–32 (2 lines → ~12 lines).

No changes to `new-feature`, `session-controls`, or any other file.

---

## Verification

1. **Happy path (clean):** Run `done-feature` from a clean worktree — behavior unchanged, no prompt shown. `git worktree list` no longer shows the worktree; `git branch --list $BRANCH` returns nothing; workspace is closed.
2. **Dirty, confirm discard:** Add an uncommitted file to the worktree, run `done-feature`, enter `y` — `git worktree list` no longer shows the worktree; `git branch --list $BRANCH` returns nothing; workspace is closed.
3. **Dirty, abort:** Add an uncommitted file, run `done-feature`, enter `n` (or press Enter) — script exits 0; `git worktree list` still shows the worktree; `git branch --list $BRANCH` still returns the branch; workspace is still open; shell is in `$MAIN_ROOT`.
4. **Default-deny:** In step 3, press Enter without typing anything — confirms the prompt defaults to `N` (no force-remove).
17 changes: 15 additions & 2 deletions done-feature
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,21 @@ echo
cd "$MAIN_ROOT"

# ── 4. Remove the worktree ───────────────────────────────────────────────────
git worktree remove "$WORKTREE_DIR"
echo "Removed worktree: $WORKTREE_DIR"
if git worktree remove "$WORKTREE_DIR" 2>/dev/null; then
echo "Removed worktree: $WORKTREE_DIR"
else
echo "Worktree has uncommitted changes:"
git -C "$WORKTREE_DIR" status --short
echo
read -rp "Discard changes and force remove? [y/N] " CONFIRM
if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then
git worktree remove --force "$WORKTREE_DIR"
echo "Force removed worktree: $WORKTREE_DIR"
else
echo "Aborted. Worktree kept: $WORKTREE_DIR"
exit 0
fi
fi

# ── 5. Delete the branch ─────────────────────────────────────────────────────
if git branch -d "$BRANCH" 2>/dev/null; then
Expand Down