{"record":{"id":"2e1858e7ed9c71a8","repo":"gastownhall/beads","slug":"target-directory-identity-changed","errorCode":null,"errorMessage":"target directory identity changed","messagePattern":"target directory identity changed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/worktree_cmd.go","lineNumber":1449,"sourceCode":"\t\t}\n\t\tfacts.StatusBytes = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target cleanliness changed\")}\n\t}\n\tfacts.Cleanliness = worktreeremove.InvariantStable\n\tfacts.StatusBytes = worktreeremove.InvariantStable\n\tif currentTarget.statusFingerprint != plan.target.statusFingerprint {\n\t\tfacts.DirtyFileFingerprint = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target changed files changed\")}\n\t}\n\tfacts.DirtyFileFingerprint = worktreeremove.InvariantStable\n\tif !plan.force && currentTarget.status != \"\" {\n\t\tfacts.Cleanliness = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target is no longer clean\")}\n\t}\n\tif !os.SameFile(currentTarget.pathInfo, plan.target.pathInfo) ||\n\t\t!samePinnedFileMetadata(currentTarget.pathInfo, plan.target.pathInfo) {\n\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}","sourceCodeStart":1431,"sourceCodeEnd":1467,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/worktree_cmd.go#L1431-L1467","documentation":"bd throws this error when the target worktree's directory no longer refers to the same filesystem inode/pinned metadata as when the plan was built (checked via os.SameFile plus pinned-file metadata). The path was deleted and recreated, replaced by a symlink or bind mount, or otherwise swapped, so bd treats the target as a different directory and aborts rather than operating on an unexpected location.","triggerScenarios":"Executing a planned worktree operation after the target path was removed and re-created (`git worktree remove` + re-add), replaced by a symlink, moved and restored, or its gitdir admin file metadata changed — so os.SameFile(currentPathInfo, planPathInfo) is false.","commonSituations":"A script or CI deleted and recreated the worktree between plan and execution; the directory was restored from a backup or moved across filesystems; a container/volume remount replaced the path; another tool re-initialized the worktree while a bd operation was pending.","solutions":["Re-run the bd command so a fresh plan captures the new directory identity (new inode/pathInfo)","Verify the path with `git worktree list` and `ls -ld <path>`; if it was recreated unintentionally, restore the original worktree via `git worktree add` at the same commit","If the path is now a symlink or different mount, point bd at the canonical real directory instead","Serialize any external scripts that remove/recreate the worktree with bd operations"],"exampleFix":"// before: worktree deleted and recreated between plan and execution\nplan := snapshot(wt) // inode 1001\nrun(\"git\", \"worktree\", \"remove\", wt); run(\"git\", \"worktree\", \"add\", wt, ref) // inode 2002\nexecute(plan) // error: target directory identity changed\n// after: re-plan against the recreated directory\nplan := snapshot(wt) // fresh pathInfo\nexecute(plan) // succeeds","handlingStrategy":"validation","validationCode":"info1, _ := os.Stat(plannedPath)\ninfo2, err := os.Stat(wt)\nif err != nil || !os.SameFile(info1, info2) {\n    // path was replaced/recreated; re-plan before calling bd\n}","typeGuard":"func sameDirectory(plannedPath string, wt string) bool {\n    a, err1 := os.Stat(plannedPath)\n    b, err2 := os.Stat(wt)\n    return err1 == nil && err2 == nil && os.SameFile(a, b)\n}","tryCatchPattern":"err := bdRemoveWorktree(plan)\nif err != nil && strings.Contains(err.Error(), \"target directory identity changed\") {\n    plan = rebuildPlan(plan.Target) // re-stat the new directory\n    err = bdRemoveWorktree(plan)\n}","preventionTips":["Don't remove/recreate the worktree while a bd plan is pending","Resolve symlinks to the canonical path before planning (filepath.EvalSymlinks)","Avoid moving worktrees across filesystems or remounting volumes mid-operation","Run `git worktree list` to confirm the registration matches the on-disk directory before executing"],"tags":["git","worktree","filesystem","inode","stale-state","safety-check"],"backgroundTag":"git-worktree-path-changed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}