{"record":{"id":"2aa82581345ad7bf","repo":"gastownhall/beads","slug":"gitignore-changed-before-removal-w","errorCode":null,"errorMessage":".gitignore changed before removal: %w","messagePattern":"\\.gitignore changed before removal: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/worktree_cmd.go","lineNumber":1477,"sourceCode":"\t\t!samePinnedFileMetadata(currentTarget.gitMarkerInfo, plan.target.gitMarkerInfo) {\n\t\tfacts.GitMarker = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target git marker identity changed\")}\n\t}\n\tfacts.GitMarker = worktreeremove.InvariantStable\n\tif currentTarget.gitDirFingerprint != plan.target.gitDirFingerprint {\n\t\tfacts.GitAdminDirectoryBytes = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target git directory identity changed (contents mismatch)\")}\n\t}\n\tfacts.GitAdminDirectoryBytes = worktreeremove.InvariantStable\n\tif currentTarget.gitMarkerFingerprint != plan.target.gitMarkerFingerprint {\n\t\tfacts.GitMarkerBytes = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"registered target identity changed (git marker mismatch)\")}\n\t}\n\tfacts.GitMarkerBytes = worktreeremove.InvariantStable\n\tif plan.gitignoreCleanup != nil {\n\t\tif err := plan.gitignoreCleanup.validate(); err != nil {\n\t\t\tfacts.ManagedIgnore = worktreeremove.InvariantChanged\n\t\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\".gitignore changed before removal: %w\", err)}\n\t\t}\n\t}\n\tfacts.ManagedIgnore = worktreeremove.InvariantStable\n\n\tif plan.comparator == nil {\n\t\tfacts.Comparator = worktreeremove.InvariantNotRequired\n\t\tfacts.Containment = worktreeremove.InvariantNotRequired\n\t\treturn worktreeRevalidationObservation{facts: facts}\n\t}\n\tcurrentComparator, err := plan.resolveComparator(ctx, currentTarget)\n\tif err != nil {\n\t\treturn worktreeRevalidationObservation{facts: facts, err: err}\n\t}\n\tif currentComparator != *plan.comparator {\n\t\tfacts.Comparator = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\n\t\t\t\"comparison target changed (was %s, now %s)\",\n\t\t\tplan.comparator.oid,","sourceCodeStart":1459,"sourceCodeEnd":1495,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/worktree_cmd.go#L1459-L1495","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before: .gitignore edited after planning\nplan := buildRemovalPlan(target) // pins managed gitignore block\neditGitignore(\"remove old entries\") // rewrites managed block\nexecuteRemoval(plan) // fails: .gitignore changed before removal\n// after: settle .gitignore, then plan and remove\nsettleGitignoreChanges()\nplan := buildRemovalPlan(target)\nexecuteRemoval(plan)","handlingStrategy":"validation","validationCode":"// Before planning removal, confirm the .gitignore still contains the managed block:\ndata, err := os.ReadFile(gitignorePath)\nif err != nil { return err }\nif !strings.Contains(string(data), bdManagedIgnoreBlock) {\n    return fmt.Errorf(\"bd-managed .gitignore block missing; settle edits first\")\n}","typeGuard":null,"tryCatchPattern":"err := bdWorktreeRemove(path)\nvar wrappedErr string\nif err != nil && strings.Contains(err.Error(), \".gitignore changed before removal\") {\n    // review the .gitignore, restore/re-acknowledge, then retry with fresh plan\n    reviewGitignore(gitignorePath)\n    err = rebuildPlanAndRemove(path)\n}","preventionTips":["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"],"tags":["git","worktree","gitignore","race-condition"],"backgroundTag":"gitignore-changed-before-write","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}