gastownhall/beads · error
registered target identity changed
Error message
registered target identity changed
What it means
During revalidation, bd recomputes a stable identity for the registered target worktree (path plus lock/lockReason/prunable/pruneReason). If it differs from the identity captured when the plan was built, the registry entry was mutated underneath bd — for example the worktree was locked, unlocked, or became prunable — and bd aborts rather than removing a worktree under changed conditions.
Source
Thrown at cmd/bd/worktree_cmd.go:1401
func (plan *worktreeRemovalPlan) observeRevalidation(ctx context.Context) worktreeRevalidationObservation {
facts := worktreeremove.RevalidationFacts{}
worktrees, err := listRegisteredWorktrees(ctx, plan.git, plan.executionRoot)
if err != nil {
return worktreeRevalidationObservation{facts: facts, err: err}
}
currentEntry, found := findRegisteredWorktreeByPath(worktrees, plan.target.path)
if !found {
facts.Registration = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target is no longer registered at %s", plan.target.path)}
}
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")}
}View on GitHub (pinned to 71377f2769)
Solutions
- Re-run `bd worktree remove` to build a fresh plan reflecting the new registry state
- Check `git worktree list --porcelain` and compare lock/prune flags; unlock with `git worktree unlock <path>` if locking blocks the removal
- Avoid concurrent lock/unlock/prune operations on the same worktree while a removal is in flight
Example fix
// before: someone locked the worktree mid-plan $ bd worktree remove feature // error: registered target identity changed // after $ git worktree unlock /wt/feature $ bd worktree remove feature # rebuild plan against current state
Defensive patterns
Strategy: retry
Validate before calling
git worktree list --porcelain | grep -A3 '^worktree /wt/feature$' | grep -q '^locked' && { echo 'worktree locked; unlock first'; exit 1; } Type guard
func identityStable(before, after worktreeEntry) bool {
return before.locked == after.locked && before.lockReason == after.lockReason &&
before.prunable == after.prunable && before.pruneReason == after.pruneReason
} Try / catch
obs := revalidateWorktreePlan(ctx, plan)
if obs.err != nil && strings.Contains(obs.err.Error(), "identity changed") {
plan, err = buildWorktreeRemovalPlan(ctx, git, name, nil) // rebuild and retry once
} Prevention
- Avoid lock/unlock/prune during an in-flight removal
- Coordinate with teammates/agents working on the same worktree
- Check lock state with `git worktree list --porcelain` before removing
- Rebuild plans instead of reusing cached ones across sessions
When it happens
Trigger: `git worktree lock`/`unlock`/`prune` or `bd worktree lock/unlock` executed on the target between building the removal plan and executing it; another process altering the worktree's prune state (e.g. the directory temporarily disappearing and reappearing).
Common situations: A teammate locked the worktree while you were removing it; an IDE or agent locked worktrees automatically; a prune-capable state appeared because the directory was briefly renamed or unmounted.
Related errors
- target HEAD disagrees with git worktree registry (registry %
- target is no longer registered at %s
- target git directory changed
- target common git directory changed
- target HEAD changed from %s to %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/5fd08b6d2529e838.
Report an issue: GitHub.