gastownhall/beads · error
target HEAD changed from %s to %s
Error message
target HEAD changed from %s to %s
What it means
This error comes from the worktree removal/revalidation flow in bd. Before executing a planned worktree operation, bd re-validates that the target worktree is in the exact state observed when the plan was built; if the target's HEAD OID differs from the planned HEAD OID, the plan is considered stale and the operation aborts with this error. It is a deliberate safety guard against destroying or modifying a worktree that has moved to a different commit since planning.
Source
Thrown at cmd/bd/worktree_cmd.go:1423
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
}
facts.StatusBytes = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target cleanliness changed")}
}
facts.Cleanliness = worktreeremove.InvariantStable
facts.StatusBytes = worktreeremove.InvariantStable
if currentTarget.statusFingerprint != plan.target.statusFingerprint {
facts.DirtyFileFingerprint = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target changed files changed")}
}
facts.DirtyFileFingerprint = worktreeremove.InvariantStableView on GitHub (pinned to 71377f2769)
Solutions
- Re-run the bd command so a fresh plan is built against the current HEAD
- Verify the worktree's expected commit with `git -C <worktree> rev-parse HEAD` and restore the planned commit (git checkout/reset) if the change was unintentional
- If the HEAD change is intentional and safe, use the force option (plan.force) so the operation proceeds despite the drift
- Check for concurrent processes (other bd sessions, IDEs, cron git jobs) touching the worktree and serialize operations
Example fix
// before: plan built earlier, then executed later against a moved HEAD obs := revalidate(plan) // errors: target HEAD changed from abc123 to def456 // after: re-plan immediately before executing plan := buildWorktreeRemovePlan(target) // fresh snapshot obs := revalidate(plan) // passes because HEAD is sampled just before execution
Defensive patterns
Strategy: validation
Validate before calling
currentHEAD, _ := exec.Command("git", "-C", wt, "rev-parse", "HEAD").Output()
if strings.TrimSpace(string(currentHEAD)) != plannedHeadOID {
// re-plan or abort before invoking the bd operation
} Type guard
func headUnchanged(planned string, wt string) bool {
out, err := exec.Command("git", "-C", wt, "rev-parse", "HEAD").Output()
return err == nil && strings.TrimSpace(string(out)) == planned
} Try / catch
err := bdRemoveWorktree(plan)
if err != nil && strings.Contains(err.Error(), "target HEAD changed") {
plan = rebuildPlan(plan.Target) // re-snapshot and retry once
err = bdRemoveWorktree(plan)
} Prevention
- Snapshot the worktree state immediately before executing, not long in advance
- Avoid running git checkout/pull/rebase in the worktree while a bd operation is pending
- Serialize bd operations with other automation touching the same worktree (locks or a job queue)
- If HEAD movement is expected, use the force option explicitly rather than fighting the guard
When it happens
Trigger: Running a bd worktree remove (or similar planned mutation) when `git -C <worktree> rev-parse HEAD` at execution time returns a different commit than at plan time — e.g. a checkout, pull, rebase, reset, or commit landed on the target worktree between planning and execution, or by a concurrent process.
Common situations: A background fetch/auto-update or IDE checked out another branch in the worktree after bd captured its state; the user ran git commands in the worktree while a bd command was queued or scripted; a CI/script re-planned long after snapshotting the worktree; another agent session committed to the worktree concurrently.
Related errors
- target is no longer registered at %s
- target git directory changed
- target cleanliness changed
- target directory identity changed
- target HEAD disagrees with git worktree registry (registry %
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3a71fce0b6b72cad.
Report an issue: GitHub.