diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index a65c16ae3..bb1227a92 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -69,11 +69,13 @@ import { getProjectTotals, formatCost, formatTokenCount, } from "./metrics.js"; import { dirname, join } from "node:path"; +import { sep as pathSep } from "node:path"; import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs"; import { execSync, execFileSync } from "node:child_process"; import { autoCommitCurrentBranch, captureIntegrationBranch, + detectWorktreeName, getCurrentBranch, getMainBranch, MergeConflictError, @@ -505,7 +507,8 @@ export async function startAuto( if (currentMilestoneId) setActiveMilestoneId(base, currentMilestoneId); // ── Auto-worktree: re-enter worktree on resume if not already inside ── - if (currentMilestoneId && originalBasePath && !isInAutoWorktree(basePath)) { + // Skip if already inside a worktree (manual /worktree) to prevent nesting. + if (currentMilestoneId && originalBasePath && !isInAutoWorktree(basePath) && !detectWorktreeName(basePath) && !detectWorktreeName(originalBasePath)) { try { const existingWtPath = getAutoWorktreePath(originalBasePath, currentMilestoneId); if (existingWtPath) { @@ -668,8 +671,22 @@ export async function startAuto( // ── Auto-worktree: create or enter worktree for the active milestone ── // Store the original project root before any chdir so we can restore on stop. + // Skip if already inside a worktree (manual /worktree or another auto-worktree) + // to prevent nested worktree creation. originalBasePath = base; - if (currentMilestoneId) { + + const isUnderGsdWorktrees = (p: string): boolean => { + // Prevent creating nested auto-worktrees when running from within any + // `.gsd/worktrees/...` directory (including manual worktrees). + const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`; + if (p.includes(marker)) { + return true; + } + const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`; + return p.endsWith(worktreesSuffix); + }; + + if (currentMilestoneId && !detectWorktreeName(base) && !isUnderGsdWorktrees(base)) { try { const existingWtPath = getAutoWorktreePath(base, currentMilestoneId); if (existingWtPath) {