From 16bda686a161a2c244b3fcfa1bef7cbc828c80fe Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 17 Mar 2026 10:19:37 -0400 Subject: [PATCH] fix: sync project root artifacts into worktree before deriveState to prevent stale DB loop (#853) (#855) --- src/resources/extensions/gsd/auto.ts | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index 817337287..ad38c8a27 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -166,6 +166,41 @@ import { hasPendingCaptures, loadPendingCaptures, countPendingCaptures } from ". // auto-mode reads stale state from the project root and re-dispatches // already-completed units. +/** + * Sync milestone artifacts from project root INTO worktree before deriveState. + * Covers the case where the LLM wrote artifacts to the main repo filesystem + * (e.g. via absolute paths) but the worktree has stale data. Also deletes + * gsd.db in the worktree so it rebuilds from fresh disk state (#853). + * Non-fatal — sync failure should never block dispatch. + */ +function syncProjectRootToWorktree(projectRoot: string, worktreePath: string, milestoneId: string | null): void { + if (!worktreePath || !projectRoot || worktreePath === projectRoot) return; + if (!milestoneId) return; + + const prGsd = join(projectRoot, ".gsd"); + const wtGsd = join(worktreePath, ".gsd"); + + // Copy milestone directory from project root to worktree if the project root + // has newer artifacts (e.g. slices that don't exist in the worktree yet) + try { + const srcMilestone = join(prGsd, "milestones", milestoneId); + const dstMilestone = join(wtGsd, "milestones", milestoneId); + if (existsSync(srcMilestone)) { + mkdirSync(dstMilestone, { recursive: true }); + cpSync(srcMilestone, dstMilestone, { recursive: true, force: false }); + } + } catch { /* non-fatal */ } + + // Delete worktree gsd.db so it rebuilds from the freshly synced files. + // Stale DB rows are the root cause of the infinite skip loop (#853). + try { + const wtDb = join(wtGsd, "gsd.db"); + if (existsSync(wtDb)) { + unlinkSync(wtDb); + } + } catch { /* non-fatal */ } +} + /** * Sync dispatch-critical .gsd/ state files from worktree to project root. * Only runs when inside an auto-worktree (worktreePath differs from projectRoot). @@ -2080,6 +2115,14 @@ async function dispatchNextUnit( // Non-fatal — health gate failure should never block dispatch } + // ── Sync project root artifacts into worktree (#853) ───────────────── + // When the LLM writes artifacts to the main repo filesystem instead of + // the worktree, the worktree's gsd.db becomes stale. Sync before + // deriveState to ensure the worktree has the latest artifacts. + if (originalBasePath && basePath !== originalBasePath && currentMilestoneId) { + syncProjectRootToWorktree(originalBasePath, basePath, currentMilestoneId); + } + const stopDeriveTimer = debugTime("derive-state"); let state = await deriveState(basePath); stopDeriveTimer({