From 2b031b9441f0c9061f8a6ad3c3a27f5753882dd3 Mon Sep 17 00:00:00 2001 From: "Charles P. Cross" <8572939+cpdata@users.noreply.github.com> Date: Thu, 18 Dec 2025 23:29:32 -0500 Subject: [PATCH] fix(worktree): add -f flag to handle missing but registered state (#609) Problem: When the daemon auto-sync runs with --auto-commit --auto-push, the sync branch pull operation consistently fails with: fatal: '.git/beads-worktrees/beads-metadata' is a missing but already registered worktree; use 'add -f' to override, or 'prune' or 'remove' to clear This occurs because: 1. Daemon creates worktree at .git/beads-worktrees/ 2. Git registers it in .git/worktrees/ 3. After the operation, worktree contents are removed 4. Git registration persists, pointing to the now-empty path 5. Subsequent CreateBeadsWorktree calls fail because os.Stat() returns error (path missing), so no cleanup happens, then git worktree add fails because git still has it registered Root cause: The git worktree add commands in CreateBeadsWorktree() did not use the -f (force) flag, which is needed to override the "missing but already registered" state. Solution: Add -f flag to both git worktree add commands (for existing branch and new branch cases). Per git documentation, -f overrides the safeguard that prevents creating a worktree when the path is already registered but missing. The existing git worktree prune call (line 30-32) was intended to handle this, but it runs before the path check and may not always clear the registration in time. The -f flag provides a robust fallback. Testing: - All existing worktree tests pass - Added regression test TestCreateBeadsWorktree_MissingButRegistered that simulates the exact issue #609 scenario Fixes #609 Co-authored-by: Charles P. Cross --- internal/git/worktree.go | 6 ++-- internal/git/worktree_test.go | 62 +++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/internal/git/worktree.go b/internal/git/worktree.go index 3f3da7b5..52f7b9cd 100644 --- a/internal/git/worktree.go +++ b/internal/git/worktree.go @@ -52,13 +52,15 @@ func (wm *WorktreeManager) CreateBeadsWorktree(branch, worktreePath string) erro branchExists := wm.branchExists(branch) // Create worktree without checking out files initially + // Use -f (force) to handle "missing but already registered" state (issue #609) + // This occurs when the worktree directory was deleted but git registration persists var cmd *exec.Cmd if branchExists { // Checkout existing branch - cmd = exec.Command("git", "worktree", "add", "--no-checkout", worktreePath, branch) + cmd = exec.Command("git", "worktree", "add", "-f", "--no-checkout", worktreePath, branch) } else { // Create new branch - cmd = exec.Command("git", "worktree", "add", "--no-checkout", "-b", branch, worktreePath) + cmd = exec.Command("git", "worktree", "add", "-f", "--no-checkout", "-b", branch, worktreePath) } cmd.Dir = wm.repoPath diff --git a/internal/git/worktree_test.go b/internal/git/worktree_test.go index 9e046b24..35076d69 100644 --- a/internal/git/worktree_test.go +++ b/internal/git/worktree_test.go @@ -1056,3 +1056,65 @@ func TestIsWorktree(t *testing.T) { } }) } + +// TestCreateBeadsWorktree_MissingButRegistered tests the issue #609 scenario where +// the worktree directory is deleted but git still has it registered in .git/worktrees/. +// The -f flag on git worktree add should handle this gracefully. +func TestCreateBeadsWorktree_MissingButRegistered(t *testing.T) { + repoPath, cleanup := setupTestRepo(t) + defer cleanup() + + wm := NewWorktreeManager(repoPath) + worktreePath := filepath.Join(t.TempDir(), "beads-worktree-gh609") + branch := "beads-metadata-gh609" + + // Step 1: Create a worktree + if err := wm.CreateBeadsWorktree(branch, worktreePath); err != nil { + t.Fatalf("Initial CreateBeadsWorktree failed: %v", err) + } + + // Verify it exists and is registered + if _, err := os.Stat(worktreePath); os.IsNotExist(err) { + t.Fatal("Worktree was not created") + } + + // Step 2: Manually delete the worktree directory (simulating the bug scenario) + // but leave the git registration in .git/worktrees/ + if err := os.RemoveAll(worktreePath); err != nil { + t.Fatalf("Failed to remove worktree directory: %v", err) + } + + // Verify the directory is gone + if _, err := os.Stat(worktreePath); !os.IsNotExist(err) { + t.Fatal("Worktree directory should not exist after removal") + } + + // Verify git still has it registered (this is the "missing but registered" state) + cmd := exec.Command("git", "worktree", "list", "--porcelain") + cmd.Dir = repoPath + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Failed to list worktrees: %v", err) + } + if !strings.Contains(string(output), "worktree") { + t.Log("Note: git worktree list shows no worktrees - prune may have run") + } + + // Step 3: Try to recreate the worktree - this should succeed with -f flag (issue #609 fix) + // Without the fix, this would fail with: + // "fatal: '.git/beads-worktrees/...' is a missing but already registered worktree" + if err := wm.CreateBeadsWorktree(branch, worktreePath); err != nil { + t.Errorf("CreateBeadsWorktree failed for missing-but-registered worktree (issue #609): %v", err) + } + + // Verify the worktree was recreated successfully + if _, err := os.Stat(worktreePath); os.IsNotExist(err) { + t.Error("Worktree was not recreated after issue #609 fix") + } + + // Verify it's a valid worktree + valid, err := wm.isValidWorktree(worktreePath) + if err != nil || !valid { + t.Errorf("Recreated worktree should be valid: valid=%v, err=%v", valid, err) + } +}