gastownhall/beads · error
target git directory changed
Error message
target git directory changed
What it means
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.
Source
Thrown at cmd/bd/worktree_cmd.go:1413
if worktreeRegistryIdentity(currentEntry) != plan.target.registryID {
facts.Registration = worktreeremove.InvariantChanged
if currentEntry.locked != plan.target.locked || currentEntry.lockReason != plan.target.lockReason ||
currentEntry.prunable != plan.target.prunable || currentEntry.pruneReason != plan.target.pruneReason {
facts.LockPrune = worktreeremove.InvariantChanged
}
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("registered target identity changed")}
}
facts.Registration = worktreeremove.InvariantStable
facts.LockPrune = worktreeremove.InvariantStable
facts.TargetPath = worktreeremove.InvariantStable
currentTarget, err := inspectWorktreeTarget(ctx, plan.git, currentEntry)
if err != nil {
return worktreeRevalidationObservation{facts: facts, err: err}
}
if !sameWorktreePath(currentTarget.gitDir, plan.target.gitDir) {
facts.GitAdminDirectory = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target git directory changed")}
}
facts.GitAdminDirectory = worktreeremove.InvariantStable
if !sameWorktreePath(currentTarget.commonDir, plan.commonDir) {
facts.CommonDirectory = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target common git directory changed")}
}
facts.CommonDirectory = worktreeremove.InvariantStable
if currentTarget.headOID != plan.target.headOID {
facts.Head = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target HEAD changed from %s to %s", plan.target.headOID, currentTarget.headOID)}
}
facts.Head = worktreeremove.InvariantStable
if currentTarget.status != plan.target.status {
if (currentTarget.status == "") != (plan.target.status == "") {
facts.Cleanliness = worktreeremove.InvariantChanged
} else {
facts.Cleanliness = worktreeremove.InvariantStable
}View on GitHub (pinned to 71377f2769)
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
Example fix
// before: worktree recreated at same path between plan and execute $ bd worktree remove feature // error: target git directory changed // after $ bd worktree remove feature # fresh plan against the new worktree
Defensive patterns
Strategy: retry
Validate before calling
cur=$(git -C /wt/feature rev-parse --path-format=absolute --git-dir)
[ "$cur" = "$RECORDED_GITDIR" ] || { echo 'worktree git dir changed; rebuild plan'; exit 1; } Type guard
func gitDirStable(before, after string) bool {
return filepath.Clean(before) == filepath.Clean(after)
} Try / catch
obs := revalidateWorktreePlan(ctx, plan)
if obs.err != nil && strings.Contains(obs.err.Error(), "git directory changed") {
plan, err = buildWorktreeRemovalPlan(ctx, git, name, nil) // fresh plan, then execute
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- target is no longer registered at %s
- target HEAD changed from %s to %s
- target cleanliness changed
- target HEAD disagrees with git worktree registry (registry %
- registered target identity changed
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/d48b91f1ab59c6b7.
Report an issue: GitHub.