Skip to content

Commit b044808

Browse files
Fix gt-pvx auto-save committing deletions of tracked files
Both auto-save mechanisms (gt done safety net and checkpoint_dog) use git add -A which stages deletions. A polecat whose working tree has a missing tracked file (e.g. .beads/metadata.json) gets that deletion auto-committed, breaking infrastructure for subsequent sessions. Fix: after git add -A, query staged deletions via git diff --cached --name-only --diff-filter=D and unstage them. A safety-net commit should preserve work (additions + modifications), never destroy it (deletions). Also adds runtime dir exclusions to done.go (.beads/, .claude/, .runtime/, __pycache__/) matching checkpoint_dog's existing runtimeExcludeDirs. Fixes #3615
1 parent 9f962c4 commit b044808

File tree

3 files changed

+42
-1
lines changed

3 files changed

+42
-1
lines changed

internal/cmd/done.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,8 @@ func runDone(cmd *cobra.Command, args []string) (retErr error) {
288288
fmt.Printf("\n%s Uncommitted changes detected — auto-saving to prevent work loss\n", style.Bold.Render("⚠"))
289289
fmt.Printf(" Files: %s\n\n", workStatus.String())
290290

291-
// Stage all changes (git add -A), then unstage overlay files (gt-p35).
291+
// Stage all changes (git add -A), then unstage overlay/runtime files (gt-p35)
292+
// and any deletions of tracked files (gt-pvx safety: never commit deletions).
292293
if addErr := g.Add("-A"); addErr != nil {
293294
style.PrintWarning("auto-commit: git add failed: %v — uncommitted work may be at risk", addErr)
294295
} else {
@@ -301,6 +302,18 @@ func runDone(cmd *cobra.Command, args []string) (retErr error) {
301302
_ = g.ResetFiles("CLAUDE.md")
302303
}
303304
}
305+
// Unstage runtime/ephemeral directories (mirrors checkpoint_dog exclusions).
306+
for _, dir := range []string{".beads/", ".claude/", ".runtime/", "__pycache__/"} {
307+
_ = g.ResetFiles(dir)
308+
}
309+
// Unstage deletions of tracked files. A safety-net auto-commit should
310+
// preserve work (additions + modifications), never destroy it (deletions).
311+
// This prevents the bug where a polecat's working tree has a missing
312+
// tracked file (e.g. .beads/metadata.json) and the auto-save commits
313+
// the deletion, breaking infrastructure for subsequent sessions.
314+
if stagedDeletions, delErr := g.StagedDeletions(); delErr == nil && len(stagedDeletions) > 0 {
315+
_ = g.ResetFiles(stagedDeletions...)
316+
}
304317
// Build a descriptive commit message
305318
autoMsg := "fix: auto-save uncommitted implementation work (gt-pvx safety net)"
306319
if issueFromBranch := parseBranchName(branch).Issue; issueFromBranch != "" {

internal/daemon/checkpoint_dog.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,20 @@ func (d *Daemon) checkpointWorktree(workDir, rigName, polecatName string) bool {
143143
_, _ = runGitCmd(workDir, "reset", "HEAD", "--", dir)
144144
}
145145

146+
// Unstage deletions of tracked files. A checkpoint should preserve work
147+
// (additions + modifications), never commit deletions of tracked files.
148+
// This prevents the bug where a polecat's working tree has a missing
149+
// tracked file and the checkpoint commits the deletion (gt-pvx fix).
150+
if delOut, err := runGitCmd(workDir, "diff", "--cached", "--name-only", "--diff-filter=D"); err == nil {
151+
if dels := strings.TrimSpace(delOut); dels != "" {
152+
for _, f := range strings.Split(dels, "\n") {
153+
if f != "" {
154+
_, _ = runGitCmd(workDir, "reset", "HEAD", "--", f)
155+
}
156+
}
157+
}
158+
}
159+
146160
// Check if anything is staged after exclusions
147161
diffOut, err := runGitCmd(workDir, "diff", "--cached", "--quiet")
148162
if err == nil && strings.TrimSpace(diffOut) == "" {

internal/git/git.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,20 @@ func (g *Git) ResetFiles(paths ...string) error {
555555
return err
556556
}
557557

558+
// StagedDeletions returns the list of tracked files staged for deletion.
559+
// Used by auto-save to unstage deletions — safety nets should preserve work, not destroy it.
560+
func (g *Git) StagedDeletions() ([]string, error) {
561+
out, err := g.run("diff", "--cached", "--name-only", "--diff-filter=D")
562+
if err != nil {
563+
return nil, err
564+
}
565+
trimmed := strings.TrimSpace(out)
566+
if trimmed == "" {
567+
return nil, nil
568+
}
569+
return strings.Split(trimmed, "\n"), nil
570+
}
571+
558572
// ShowFile returns the contents of a file at a given ref (e.g., "origin/main:CLAUDE.md").
559573
// Returns empty string and no error if the file does not exist at that ref.
560574
func (g *Git) ShowFile(ref, path string) (string, error) {

0 commit comments

Comments
 (0)