fix(worktree): add pre-merge deletion guard to quick.md; fix backup handling on conflict (#2275)

Three gaps in the orchestrator file-protection block (#1756, #2040):

1. quick.md never received the pre-merge deletion guard added to
   execute-phase.md in #2040. Added the same DELETIONS check: if the
   worktree branch deletes any tracked .planning/ files, block the merge
   with a clear message rather than silently losing those files.

2. Both workflows deleted STATE_BACKUP and ROADMAP_BACKUP on merge
   conflict — destroying the recovery files at exactly the moment they
   were needed. Changed conflict handler to: preserve both backup paths,
   print restore instructions, and break (halt) instead of continue
   (silently advancing to the next worktree).

3. Neither workflow used --no-ff. Without it a fast-forward merge
   produces no merge commit, so HEAD~1 in the resurrection check points
   to the worktree's parent rather than main's pre-merge HEAD. Added
   --no-ff to both git merge calls so HEAD~1 is always reliable.

Closes #2208

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-04-15 14:58:43 -04:00
committed by GitHub
parent 32ab8ac77e
commit e1fe12322c
2 changed files with 20 additions and 7 deletions

View File

@@ -614,11 +614,13 @@ Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZAT
continue
fi
# Merge the worktree branch into the current branch
git merge "$WT_BRANCH" --no-edit -m "chore: merge executor worktree ($WT_BRANCH)" 2>&1 || {
# Merge the worktree branch into the current branch (--no-ff ensures a merge commit so HEAD~1 is reliable)
git merge "$WT_BRANCH" --no-ff --no-edit -m "chore: merge executor worktree ($WT_BRANCH)" 2>&1 || {
echo "⚠ Merge conflict from worktree $WT_BRANCH — resolve manually"
rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
continue
echo " STATE.md backup: $STATE_BACKUP"
echo " ROADMAP.md backup: $ROADMAP_BACKUP"
echo " Restore with: cp \$STATE_BACKUP .planning/STATE.md && cp \$ROADMAP_BACKUP .planning/ROADMAP.md"
break
}
# Restore orchestrator-owned files (main always wins)

View File

@@ -628,10 +628,21 @@ After executor returns:
# Snapshot files on main to detect resurrections
PRE_MERGE_FILES=$(git ls-files .planning/)
git merge "$WT_BRANCH" --no-edit -m "chore: merge quick task worktree ($WT_BRANCH)" 2>&1 || {
echo "⚠ Merge conflict — resolve manually"
# Pre-merge deletion guard: block merges that delete tracked .planning/ files
DELETIONS=$(git diff --diff-filter=D --name-only HEAD..."$WT_BRANCH" 2>/dev/null || true)
if [ -n "$DELETIONS" ]; then
echo "BLOCKED: Worktree branch $WT_BRANCH contains file deletions: $DELETIONS"
echo "Review these deletions before merging. If intentional, remove this guard and re-run."
rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
continue
fi
git merge "$WT_BRANCH" --no-ff --no-edit -m "chore: merge quick task worktree ($WT_BRANCH)" 2>&1 || {
echo "⚠ Merge conflict from worktree $WT_BRANCH — resolve manually"
echo " STATE.md backup: $STATE_BACKUP"
echo " ROADMAP.md backup: $ROADMAP_BACKUP"
echo " Restore with: cp \$STATE_BACKUP .planning/STATE.md && cp \$ROADMAP_BACKUP .planning/ROADMAP.md"
break
}
# Restore orchestrator-owned files
@@ -639,7 +650,7 @@ After executor returns:
if [ -s "$ROADMAP_BACKUP" ]; then cp "$ROADMAP_BACKUP" .planning/ROADMAP.md; fi
rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
# Remove files deleted on main but re-added by worktree
# Remove files deleted on main but re-added by worktree (--no-ff guarantees a merge commit so HEAD~1 is reliable)
DELETED_FILES=$(git diff --diff-filter=A --name-only HEAD~1 -- .planning/ 2>/dev/null || true)
for RESURRECTED in $DELETED_FILES; do
if ! echo "$PRE_MERGE_FILES" | grep -qxF "$RESURRECTED"; then