From e1270c3547b0a292a624de23afea7be798c6d7d7 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Thu, 14 May 2026 19:02:16 -0400 Subject: [PATCH] fix(quick): pin cleanup-loop CWD to project root before bare git commands (#3521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The post-merge worktree-cleanup loop in quick.md issued bare `git diff`, `git merge`, and related commands relying on CWD = project root. An LLM orchestrator that reformats bash across separate tool calls can leak CWD into a worktree, causing the merge to silently no-op. At the top of each iteration body, resolve PROJECT_ROOT via `git -C "$WT" rev-parse --git-common-dir` and `cd "$PROJECT_ROOT"`. If the root cannot be resolved or reached, log a skip message and continue to the next manifest entry. All existing guards (pre-merge deletion guard #1756, STATE.md/ROADMAP.md backup/restore, resurrection guard #2501/#3195) remain intact after the CWD pin. Closes #3521 (bug 1 — CWD safety only; bug 2 / resurrection guard was already fixed in a6beac40 / PR #3201 and is pending reporter retest). Co-Authored-By: Claude Sonnet 4.6 --- get-shit-done/workflows/quick.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/get-shit-done/workflows/quick.md b/get-shit-done/workflows/quick.md index cbc792087..65a71d787 100644 --- a/get-shit-done/workflows/quick.md +++ b/get-shit-done/workflows/quick.md @@ -799,6 +799,19 @@ After executor returns: node -e 'const fs=require("fs");const p=process.env.QUICK_WORKTREE_MANIFEST||process.env.WAVE_WORKTREE_MANIFEST;try{if(!p)throw new Error("QUICK_WORKTREE_MANIFEST is unset");if(!fs.existsSync(p))throw new Error("manifest does not exist");const s=fs.readFileSync(p,"utf8");if(!s.trim())throw new Error("manifest is empty");const j=JSON.parse(s);for(const w of j.worktrees||[])if(w.worktree_path)console.log(w.worktree_path)}catch(e){console.error(`ERROR: cannot read worktree manifest ${p||"(unset)"}: ${e.message}`);process.exit(1)}' > "$WT_PATHS_FILE" || { echo "BLOCKED: cannot read QUICK_WORKTREE_MANIFEST; refusing cleanup (#3384)." >&2; exit 1; } while IFS= read -r WT; do [ -z "$WT" ] && continue + # Pin CWD to project root before any bare git command (#3521). + # An LLM orchestrator may leak CWD into a worktree across tool calls; without + # this pin the merge command resolves against the worktree branch itself and + # silently no-ops the main-branch merge. + PROJECT_ROOT=$(git -C "$WT" rev-parse --git-common-dir 2>/dev/null) + # git rev-parse --git-common-dir returns the .git dir, not the working tree root. + # Strip the trailing /.git (or bare .git) to get the working tree root. + PROJECT_ROOT=$(echo "$PROJECT_ROOT" | sed 's|/\.git$||; s|/\.git/.*||') + if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT" ]; then + echo "WARN: cannot resolve project root from worktree $WT — skipping cleanup for this entry (#3521)" >&2 + continue + fi + cd "$PROJECT_ROOT" || { echo "WARN: cannot cd to project root $PROJECT_ROOT — skipping cleanup for worktree $WT (#3521)" >&2; continue; } WT_BRANCH=$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null) if [ -n "$WT_BRANCH" ] && [ "$WT_BRANCH" != "HEAD" ]; then # --- Orchestrator file protection (#1756) ---