{"record":{"id":"d48b91f1ab59c6b7","repo":"gastownhall/beads","slug":"target-git-directory-changed","errorCode":null,"errorMessage":"target git directory changed","messagePattern":"target git directory changed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/worktree_cmd.go","lineNumber":1413,"sourceCode":"\tif worktreeRegistryIdentity(currentEntry) != plan.target.registryID {\n\t\tfacts.Registration = worktreeremove.InvariantChanged\n\t\tif currentEntry.locked != plan.target.locked || currentEntry.lockReason != plan.target.lockReason ||\n\t\t\tcurrentEntry.prunable != plan.target.prunable || currentEntry.pruneReason != plan.target.pruneReason {\n\t\t\tfacts.LockPrune = worktreeremove.InvariantChanged\n\t\t}\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"registered target identity changed\")}\n\t}\n\tfacts.Registration = worktreeremove.InvariantStable\n\tfacts.LockPrune = worktreeremove.InvariantStable\n\tfacts.TargetPath = worktreeremove.InvariantStable\n\n\tcurrentTarget, err := inspectWorktreeTarget(ctx, plan.git, currentEntry)\n\tif err != nil {\n\t\treturn worktreeRevalidationObservation{facts: facts, err: err}\n\t}\n\tif !sameWorktreePath(currentTarget.gitDir, plan.target.gitDir) {\n\t\tfacts.GitAdminDirectory = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target git directory changed\")}\n\t}\n\tfacts.GitAdminDirectory = worktreeremove.InvariantStable\n\tif !sameWorktreePath(currentTarget.commonDir, plan.commonDir) {\n\t\tfacts.CommonDirectory = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target common git directory changed\")}\n\t}\n\tfacts.CommonDirectory = worktreeremove.InvariantStable\n\tif currentTarget.headOID != plan.target.headOID {\n\t\tfacts.Head = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target HEAD changed from %s to %s\", plan.target.headOID, currentTarget.headOID)}\n\t}\n\tfacts.Head = worktreeremove.InvariantStable\n\tif currentTarget.status != plan.target.status {\n\t\tif (currentTarget.status == \"\") != (plan.target.status == \"\") {\n\t\t\tfacts.Cleanliness = worktreeremove.InvariantChanged\n\t\t} else {\n\t\t\tfacts.Cleanliness = worktreeremove.InvariantStable\n\t\t}","sourceCodeStart":1395,"sourceCodeEnd":1431,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/worktree_cmd.go#L1395-L1431","documentation":"Revalidation compares the target worktree's resolved git admin directory (gitDir) with the one recorded at plan time. A change means the worktree's .git link was rewritten or the worktree was recreated at the same path but pointing at different git metadata — bd's plan no longer describes reality, so it aborts with this error and marks GitAdminDirectory as Changed.","triggerScenarios":"Executing a removal plan after the target's `.git` file was rewritten (worktree deleted and re-added at the same path), a `git worktree repair`/`prune`+re-add cycle, or manual edits to the `.git` pointer file between plan and execute.","commonSituations":"Scripts that recreate worktrees at fixed paths; IDE worktree sync features; manual copying of worktree directories; running a stale bd plan against a rebuilt checkout.","solutions":["Discard the stale plan and re-run `bd worktree remove` so the plan captures the new gitDir","Inspect the target's `.git` file to see where it now points; if the worktree was recreated, this is expected","Avoid recreating worktrees while a removal is pending; serialize git worktree operations"],"exampleFix":"// before: worktree recreated at same path between plan and execute\n$ bd worktree remove feature\n// error: target git directory changed\n// after\n$ bd worktree remove feature   # fresh plan against the new worktree","handlingStrategy":"retry","validationCode":"cur=$(git -C /wt/feature rev-parse --path-format=absolute --git-dir)\n[ \"$cur\" = \"$RECORDED_GITDIR\" ] || { echo 'worktree git dir changed; rebuild plan'; exit 1; }","typeGuard":"func gitDirStable(before, after string) bool {\n\treturn filepath.Clean(before) == filepath.Clean(after)\n}","tryCatchPattern":"obs := revalidateWorktreePlan(ctx, plan)\nif obs.err != nil && strings.Contains(obs.err.Error(), \"git directory changed\") {\n\tplan, err = buildWorktreeRemovalPlan(ctx, git, name, nil) // fresh plan, then execute\n}","preventionTips":["Don't delete-and-recreate worktrees at the same path mid-operation","Serialize worktree lifecycle scripts (create/remove/repair)","Use unique paths per worktree generation instead of reusing names","Verify the target's `.git` file points where you expect before removal"],"tags":["git","worktree","race-condition","stale-state"],"backgroundTag":"stale-worktree-state","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}