gastownhall/beads · error

cannot safely prepare .gitignore cleanup: %w

Error message

cannot safely prepare .gitignore cleanup: %w

What it means

When the target worktree lives inside the primary worktree's directory tree, bd plans to clean the managed .gitignore entry that ignored it. This error wraps a failure of `prepareGitignoreCleanup` — bd could not safely compute or stage the .gitignore edit (unreadable file, unsafe relative path, write problems), and aborts the removal rather than leaving a stale ignore entry or editing the wrong file.

Source

Thrown at cmd/bd/worktree_cmd.go:1314

		GitMarker:      worktreeremove.Present,
		CommonDir:      worktreeremove.Matched,
		Head:           worktreeremove.Present,
		Status:         status,
		ManagedIgnore:  worktreeremove.IgnoreAbsent,
	}
	if !sameWorktreePath(target.commonDir, mainCommonDir) {
		plan.prepareFacts.CommonDir = worktreeremove.Unmatched
		plan.prepareErr = fmt.Errorf(
			"target common git directory %q does not match repository %q",
			target.commonDir,
			mainCommonDir,
		)
		return plan, nil
	}
	if relative, inside := relativeWorktreePath(plan.mainWorktree, target.path); inside {
		plan.gitignoreCleanup, err = prepareGitignoreCleanup(plan.mainWorktree, relative)
		if err != nil {
			return nil, fmt.Errorf("cannot safely prepare .gitignore cleanup: %w", err)
		}
		if plan.gitignoreCleanup != nil {
			plan.prepareFacts.ManagedIgnore = worktreeremove.IgnoreManaged
			plan.prepareFacts.ManagedIgnoreEntry = plan.gitignoreCleanup.entry
		}
	}

	if options.force.value {
		plan.prepareFacts.Comparator = worktreeremove.ComparatorNotRequired
		plan.prepareFacts.Containment = worktreeremove.ContainmentNotRequired
		return plan, nil
	}
	if target.status != "" {
		return plan, nil
	}

	var comparator pinnedWorktreeComparator
	if options.mergedInto.set {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check permissions on the primary worktree's .gitignore and make it writable by the current user
  2. Remove the worktree's ignore entry manually from .gitignore, then retry the removal
  3. Inspect the wrapped cause (%w): fix the specific path/symlink problem it reports (e.g. use the real, non-symlink path when creating the worktree)
  4. As a fallback, remove the worktree with `git worktree remove` and clean the .gitignore entry yourself

Example fix

// before
$ ls -l .gitignore  # -rw------- root
$ bd worktree remove nested-wt
// error: cannot safely prepare .gitignore cleanup: open .gitignore: permission denied
// after
$ sudo chown $(whoami) .gitignore && chmod u+w .gitignore
$ bd worktree remove nested-wt
Defensive patterns

Strategy: validation

Validate before calling

[ -w .gitignore ] || { echo '.gitignore not writable'; exit 1; }
realpath .gitignore >/dev/null 2>&1 || { echo '.gitignore path unsafe'; exit 1; }

Type guard

func canPrepareGitignore(mainWorktree string) bool {
	info, err := os.Stat(filepath.Join(mainWorktree, ".gitignore"))
	return err == nil && info.Mode().Perm()&0200 != 0
}

Try / catch

if err != nil && strings.Contains(err.Error(), "cannot safely prepare .gitignore cleanup") {
	return fmt.Errorf("fix .gitignore permissions or remove the ignore entry manually, then retry")
}

Prevention

When it happens

Trigger: `bd worktree remove` of a nested worktree (target path inside the main worktree) where prepareGitignoreCleanup fails: the main .gitignore is unreadable/unwritable, the relative path escapes or is ambiguous (symlinks, `..`), or concurrent modification makes the entry unsafe to match.

Common situations: .gitignore with restrictive permissions or owned by another user; symlinked worktree paths; worktree created at a path like `../repo` that resolves oddly; .gitignore with unusual encoding/huge size.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/b1f97746f764bfe2. Report an issue: GitHub.