gastownhall/beads · error

target HEAD disagrees with git worktree registry (registry %

Error message

target HEAD disagrees with git worktree registry (registry %q, target %q)

What it means

inspectWorktreeTarget compares the HEAD commit observed in the target worktree with the headOID recorded in bd's worktree registry. If they differ (or HEAD is empty), removal is refused. This prevents removing a worktree whose state changed since registration, guarding against racing mutations.

Source

Thrown at cmd/bd/worktree_cmd.go:998

		return pinnedWorktreeTarget{}, fmt.Errorf("target HEAD does not resolve to a commit: %w", err)
	}
	statusOutput, err := git.output(
		ctx,
		worktree.path,
		"status",
		"--porcelain=v1",
		"-z",
		"--untracked-files=all",
		"--ignore-submodules=none",
		"--ignored=matching",
	)
	if err != nil {
		return pinnedWorktreeTarget{}, fmt.Errorf("failed to inspect target cleanliness: %w", err)
	}

	headOID := strings.TrimSpace(string(headOutput))
	if headOID == "" || headOID != worktree.headOID {
		return pinnedWorktreeTarget{}, fmt.Errorf(
			"target HEAD disagrees with git worktree registry (registry %q, target %q)",
			worktree.headOID,
			headOID,
		)
	}
	pathInfo, err := os.Lstat(worktree.path)
	if err != nil {
		return pinnedWorktreeTarget{}, fmt.Errorf("failed to pin target directory identity: %w", err)
	}
	if pathInfo.Mode()&os.ModeSymlink != 0 || !pathInfo.IsDir() {
		return pinnedWorktreeTarget{}, fmt.Errorf("target path is not a real directory: %s", worktree.path)
	}
	gitDirInfo, err := os.Lstat(gitDir)
	if err != nil {
		return pinnedWorktreeTarget{}, fmt.Errorf("failed to pin target git directory identity: %w", err)
	}
	if gitDirInfo.Mode()&os.ModeSymlink != 0 || !gitDirInfo.IsDir() {
		return pinnedWorktreeTarget{}, fmt.Errorf("target git directory is not a real directory: %s", gitDir)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check current HEAD with `git -C <path> rev-parse HEAD` and refresh/re-register the worktree entry so the registry matches, then retry.
  2. If the movement was intentional, re-run `bd worktree remove` so a fresh inspection pins the new HEAD.
  3. Quiesce any process mutating the worktree (stop CI/agents) before removing.
  4. If the registry entry is stale or corrupt, remove it and re-register from `git worktree list`.

Example fix

// before
bd worktree remove feature-x
// error: target HEAD disagrees with git worktree registry (registry "abc123", target "def456")
// after
git -C /repo/.worktrees/feature-x rev-parse HEAD   # confirm intended state
bd worktree remove feature-x                        # re-run with refreshed registry
Defensive patterns

Strategy: retry

Validate before calling

const head = Bun.spawnSync(["git", "-C", path, "rev-parse", "HEAD"]).stdout.toString().trim();
const reg = JSON.parse(Bun.spawnSync(["bd", "worktree", "list", "--json"]).stdout.toString());
const entry = reg.find(w => w.path === path);
if (entry && entry.headOid && entry.headOid !== head) throw new Error("registry drift — refresh before removing");

Try / catch

try {
  await bd("worktree", "remove", path);
} catch (e) {
  if (String(e.message).includes("target HEAD disagrees")) {
    await bd("worktree", "remove", path); // re-run: fresh inspection pins new HEAD
  } else throw e;
}

Prevention

When it happens

Trigger: The worktree's HEAD moved (new commits, branch switch, reset) between registration and the removal call; a stale registry entry; observeRevalidation detects drift during a post-confirmation re-check.

Common situations: Someone committed or switched branches while the removal prompt was open; a CI job or agent pushed/reset the branch concurrently; registry data copied from another machine with a different HEAD; registry out of sync after a manual `git worktree add`.

Related errors


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