{"record":{"id":"1a4fd52a81cb4fd4","repo":"gastownhall/beads","slug":"target-git-directory-identity-changed-contents-mi","errorCode":null,"errorMessage":"target git directory identity changed (contents mismatch)","messagePattern":"target git directory identity changed \\(contents mismatch\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/worktree_cmd.go","lineNumber":1466,"sourceCode":"\t\tfacts.TargetDirectory = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target directory identity changed\")}\n\t}\n\tfacts.TargetDirectory = worktreeremove.InvariantStable\n\tif !os.SameFile(currentTarget.gitDirInfo, plan.target.gitDirInfo) ||\n\t\t!samePinnedFileMetadata(currentTarget.gitDirInfo, plan.target.gitDirInfo) {\n\t\tfacts.GitAdminDirectory = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target git directory identity changed\")}\n\t}\n\tfacts.GitAdminDirectory = worktreeremove.InvariantStable\n\tif !os.SameFile(currentTarget.gitMarkerInfo, plan.target.gitMarkerInfo) ||\n\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","sourceCodeStart":1448,"sourceCodeEnd":1484,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/worktree_cmd.go#L1448-L1484","documentation":"Raised during the revalidation phase of `bd worktree remove`: although the worktree's git admin directory is still the same inode (identity check passed), its CONTENTS no longer match the fingerprint captured at plan time. This means something wrote into the admin directory (e.g. HEAD moved, new refs, config edits, index updates) after the removal plan was built, so bd aborts rather than removing a directory in a state different from what was inspected.","triggerScenarios":"Calling `bd worktree remove` where, between plan and execution, any file inside the worktree's `.git` admin directory changed content: a `git commit`/`fetch`/`rebase` updated HEAD or refs, `git config` edits, gc ran, or another agent executed git commands in that worktree.","commonSituations":"A background process or IDE auto-fetching in the worktree; another agent committing in the worktree while a remove was queued; cron jobs running `git gc` or `git maintenance`; running removal from a script that also performs git operations on the same worktree.","solutions":["Re-run `bd worktree remove` so the plan is rebuilt with the current admin-directory fingerprint","Stop concurrent git activity in the worktree (auto-fetch, IDE git integration, other agents) before removing","Check `git -C <worktree> log --oneline -1` and `git worktree list` to see what changed since planning; confirm the current state is safe to remove","If the new contents include unmerged or valuable work, export it (commit/stash/branch) before removal"],"exampleFix":"// before: fetch/commit running in the worktree while plan is stale\nplan := buildRemovalPlan(target)\ngitFetchInBackground(worktree)\nexecuteRemoval(plan) // fails: contents mismatch\n// after: quiesce the worktree, then plan and remove\nwaitForGitQuiesce(worktree)\nplan := buildRemovalPlan(target)\nexecuteRemoval(plan)","handlingStrategy":"validation","validationCode":"// Ensure no git process is active in the worktree before planning removal:\nout, err := exec.Command(\"git\", \"-C\", worktreePath, \"status\", \"--porcelain\").Output()\nif err != nil { return err } // worktree must be a stable, working repo\n// Disable background maintenance that would mutate the admin dir:\nexec.Command(\"git\", \"-C\", worktreePath, \"config\", \"maintenance.auto\", \"false\").Run()","typeGuard":null,"tryCatchPattern":"err := bdWorktreeRemove(path)\nif err != nil && strings.Contains(err.Error(), \"contents mismatch\") {\n    // admin dir contents changed (HEAD/refs/config): inspect then retry\n    inspectWorktree(path)\n    err = rebuildPlanAndRemove(path)\n}","preventionTips":["Quiesce the worktree: no commits, fetches, rebase, or gc between plan and execution","Disable git auto-maintenance/background fetch for worktrees slated for removal","Run removal from a single serialized workflow, not in parallel with other git operations","Check `git -C <worktree> log -1` before and after planning to confirm HEAD is stable"],"tags":["git","worktree","fingerprint","race-condition"],"backgroundTag":"worktree-state-changed-underneath","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}