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
- Re-run `bd worktree remove` so the gitignore cleanup plan is re-validated against the current file
- Inspect `.gitignore` for the bd-managed block; restore it or confirm the new content is intentional, then retry
- Resolve concurrent `.gitignore` edits (merge conflicts, formatters) before retrying removal
- 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
- Don't edit `.gitignore` between planning and executing a worktree removal
- Exclude the bd-managed block from formatter or template regeneration of `.gitignore`
- Resolve merge conflicts in `.gitignore` before running removal
- Coordinate `.gitignore` changes with teammates/agents to avoid concurrent rewrites
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
- target HEAD disagrees with git worktree registry (registry %
- cannot safely prepare .gitignore cleanup: %w
- target is no longer registered at %s
- registered target identity changed
- target git directory changed
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/2aa82581345ad7bf.
Report an issue: GitHub.