fix(workflow): protect orchestrator files during worktree merge (#1756) (#1764)

When a worktree branch outlives a milestone transition, git merge
silently overwrites STATE.md and ROADMAP.md with stale content and
resurrects archived phase directories. Fix by backing up orchestrator
files before merge, restoring after, and detecting resurrected files.

Fixes #1761

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-04-05 11:11:38 -04:00
committed by GitHub
parent 17c65424ad
commit cfff82dcd2
3 changed files with 175 additions and 1 deletions

View File

@@ -485,12 +485,51 @@ Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZAT
if [ -n "$WT_BRANCH" ] && [ "$WT_BRANCH" != "HEAD" ]; then
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# --- Orchestrator file protection (#1756) ---
# Snapshot orchestrator-owned files BEFORE merge. If the worktree
# branch outlived a milestone transition, its versions of STATE.md
# and ROADMAP.md are stale. Main always wins for these files.
STATE_BACKUP=$(mktemp)
ROADMAP_BACKUP=$(mktemp)
git show HEAD:.planning/STATE.md > "$STATE_BACKUP" 2>/dev/null || true
git show HEAD:.planning/ROADMAP.md > "$ROADMAP_BACKUP" 2>/dev/null || true
# Snapshot list of files on main BEFORE merge to detect resurrections
PRE_MERGE_FILES=$(git ls-files .planning/)
# Merge the worktree branch into the current branch
git merge "$WT_BRANCH" --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
}
# Restore orchestrator-owned files (main always wins)
if [ -s "$STATE_BACKUP" ]; then
cp "$STATE_BACKUP" .planning/STATE.md
fi
if [ -s "$ROADMAP_BACKUP" ]; then
cp "$ROADMAP_BACKUP" .planning/ROADMAP.md
fi
rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
# Detect files deleted on main but re-added by worktree merge
# (e.g., archived phase directories that were intentionally removed)
DELETED_FILES=$(git diff --diff-filter=A --name-only HEAD~1 -- .planning/ 2>/dev/null || true)
for RESURRECTED in $DELETED_FILES; do
# Check if this file was NOT in main's pre-merge tree
if ! echo "$PRE_MERGE_FILES" | grep -qxF "$RESURRECTED"; then
git rm -f "$RESURRECTED" 2>/dev/null || true
fi
done
# Amend merge commit with restored files if any changed
if ! git diff --quiet .planning/STATE.md .planning/ROADMAP.md 2>/dev/null || \
[ -n "$DELETED_FILES" ]; then
git add .planning/STATE.md .planning/ROADMAP.md 2>/dev/null || true
git commit --amend --no-edit 2>/dev/null || true
fi
# Remove the worktree
git worktree remove "$WT" --force 2>/dev/null || true

View File

@@ -603,7 +603,41 @@ After executor returns:
for WT in $WORKTREES; do
WT_BRANCH=$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ -n "$WT_BRANCH" ] && [ "$WT_BRANCH" != "HEAD" ]; then
git merge "$WT_BRANCH" --no-edit -m "chore: merge quick task worktree ($WT_BRANCH)" 2>&1 || echo "⚠ Merge conflict — resolve manually"
# --- Orchestrator file protection (#1756) ---
# Backup STATE.md and ROADMAP.md before merge (main always wins)
STATE_BACKUP=$(mktemp)
ROADMAP_BACKUP=$(mktemp)
git show HEAD:.planning/STATE.md > "$STATE_BACKUP" 2>/dev/null || true
git show HEAD:.planning/ROADMAP.md > "$ROADMAP_BACKUP" 2>/dev/null || true
# 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"
rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
continue
}
# Restore orchestrator-owned files
if [ -s "$STATE_BACKUP" ]; then cp "$STATE_BACKUP" .planning/STATE.md; fi
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
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
git rm -f "$RESURRECTED" 2>/dev/null || true
fi
done
if ! git diff --quiet .planning/STATE.md .planning/ROADMAP.md 2>/dev/null || \
[ -n "$DELETED_FILES" ]; then
git add .planning/STATE.md .planning/ROADMAP.md 2>/dev/null || true
git commit --amend --no-edit 2>/dev/null || true
fi
git worktree remove "$WT" --force 2>/dev/null || true
git branch -D "$WT_BRANCH" 2>/dev/null || true
fi