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.InvariantStable

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run the bd command so a fresh plan is built against the current HEAD
  2. 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
  3. If the HEAD change is intentional and safe, use the force option (plan.force) so the operation proceeds despite the drift
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/3a71fce0b6b72cad. Report an issue: GitHub.