gastownhall/beads · error

.gitignore changed before removal: %w

Error message

.gitignore changed before removal: %w

What it means

Raised during `bd worktree remove` revalidation when the plan includes cleanup of a bd-managed `.gitignore` entry (the ignore line bd added when creating the worktree) but the `.gitignore` no longer validates against the snapshot taken at plan time. The wrapped error from `gitignoreCleanup.validate()` explains what changed (entry removed, file rewritten, or comment/marker text altered). bd aborts so it never edits a file whose expected contents it can no longer guarantee.

Source

Thrown at cmd/bd/worktree_cmd.go:1477

		!samePinnedFileMetadata(currentTarget.gitMarkerInfo, plan.target.gitMarkerInfo) {
		facts.GitMarker = worktreeremove.InvariantChanged
		return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target git marker identity changed")}
	}
	facts.GitMarker = worktreeremove.InvariantStable
	if currentTarget.gitDirFingerprint != plan.target.gitDirFingerprint {
		facts.GitAdminDirectoryBytes = worktreeremove.InvariantChanged
		return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target git directory identity changed (contents mismatch)")}
	}
	facts.GitAdminDirectoryBytes = worktreeremove.InvariantStable
	if currentTarget.gitMarkerFingerprint != plan.target.gitMarkerFingerprint {
		facts.GitMarkerBytes = worktreeremove.InvariantChanged
		return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("registered target identity changed (git marker mismatch)")}
	}
	facts.GitMarkerBytes = worktreeremove.InvariantStable
	if plan.gitignoreCleanup != nil {
		if err := plan.gitignoreCleanup.validate(); err != nil {
			facts.ManagedIgnore = worktreeremove.InvariantChanged
			return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(".gitignore changed before removal: %w", err)}
		}
	}
	facts.ManagedIgnore = worktreeremove.InvariantStable

	if plan.comparator == nil {
		facts.Comparator = worktreeremove.InvariantNotRequired
		facts.Containment = worktreeremove.InvariantNotRequired
		return worktreeRevalidationObservation{facts: facts}
	}
	currentComparator, err := plan.resolveComparator(ctx, currentTarget)
	if err != nil {
		return worktreeRevalidationObservation{facts: facts, err: err}
	}
	if currentComparator != *plan.comparator {
		facts.Comparator = worktreeremove.InvariantChanged
		return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(
			"comparison target changed (was %s, now %s)",
			plan.comparator.oid,

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run `bd worktree remove` so the gitignore cleanup plan is re-validated against the current file
  2. Inspect `.gitignore` for the bd-managed block; restore it or confirm the new content is intentional, then retry
  3. Resolve concurrent `.gitignore` edits (merge conflicts, formatters) before retrying removal
  4. If the managed entry is already gone and that is fine, verify the worktree removal completes cleanly with a fresh plan (cleanup becomes a no-op or is re-planned)

Example fix

// before: .gitignore edited after planning
plan := buildRemovalPlan(target) // pins managed gitignore block
editGitignore("remove old entries") // rewrites managed block
executeRemoval(plan) // fails: .gitignore changed before removal
// after: settle .gitignore, then plan and remove
settleGitignoreChanges()
plan := buildRemovalPlan(target)
executeRemoval(plan)
Defensive patterns

Strategy: validation

Validate before calling

// Before planning removal, confirm the .gitignore still contains the managed block:
data, err := os.ReadFile(gitignorePath)
if err != nil { return err }
if !strings.Contains(string(data), bdManagedIgnoreBlock) {
    return fmt.Errorf("bd-managed .gitignore block missing; settle edits first")
}

Try / catch

err := bdWorktreeRemove(path)
var wrappedErr string
if err != nil && strings.Contains(err.Error(), ".gitignore changed before removal") {
    // review the .gitignore, restore/re-acknowledge, then retry with fresh plan
    reviewGitignore(gitignorePath)
    err = rebuildPlanAndRemove(path)
}

Prevention

When it happens

Trigger: Calling `bd worktree remove` where, between plan and execution, someone edited the root `.gitignore`: removed or reformatted the bd-managed block, appended conflicting entries, a formatter/merge rewrote the file, or another agent modified ignore rules concurrently.

Common situations: A teammate or merge commit updated `.gitignore` while a remove was pending; a formatter (prettier, editor-on-save) normalized the file; another agent regenerated `.gitignore` from a template; manual cleanup of 'stale' ignore entries accidentally removed the bd marker block.

Related errors


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