gastownhall/beads · error

target git marker identity changed

Error message

target git marker identity changed

What it means

This error comes from the same pre-execution revalidation in `bd worktree remove`. bd pins the FileInfo of the worktree's git marker (the `.git` file or the marker distinguishing the linked worktree) at plan time and, at execution time, checks `os.SameFile` plus mode/size/mtime. If the marker file was deleted and recreated (new inode) or its metadata changed, bd refuses to proceed: the removal plan was authorized against a different filesystem object than the one now present.

Source

Thrown at cmd/bd/worktree_cmd.go:1461

		facts.Cleanliness = worktreeremove.InvariantChanged
		return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target is no longer clean")}
	}
	if !os.SameFile(currentTarget.pathInfo, plan.target.pathInfo) ||
		!samePinnedFileMetadata(currentTarget.pathInfo, plan.target.pathInfo) {
		facts.TargetDirectory = worktreeremove.InvariantChanged
		return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target directory identity changed")}
	}
	facts.TargetDirectory = worktreeremove.InvariantStable
	if !os.SameFile(currentTarget.gitDirInfo, plan.target.gitDirInfo) ||
		!samePinnedFileMetadata(currentTarget.gitDirInfo, plan.target.gitDirInfo) {
		facts.GitAdminDirectory = worktreeremove.InvariantChanged
		return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target git directory identity changed")}
	}
	facts.GitAdminDirectory = worktreeremove.InvariantStable
	if !os.SameFile(currentTarget.gitMarkerInfo, plan.target.gitMarkerInfo) ||
		!samePinnedFileMetadata(currentTarget.gitMarkerInfo, plan.target.gitMarkerInfo) {
		facts.GitMarker = worktreeremove.InvariantChanged
		return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target git marker identity changed")}
	}
	facts.GitMarker = worktreeremove.InvariantStable
	if currentTarget.gitDirFingerprint != plan.target.gitDirFingerprint {
		facts.GitAdminDirectoryBytes = worktreeremove.InvariantChanged
		return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target git directory identity changed (contents mismatch)")}
	}
	facts.GitAdminDirectoryBytes = worktreeremove.InvariantStable
	if currentTarget.gitMarkerFingerprint != plan.target.gitMarkerFingerprint {
		facts.GitMarkerBytes = worktreeremove.InvariantChanged
		return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("registered target identity changed (git marker mismatch)")}
	}
	facts.GitMarkerBytes = worktreeremove.InvariantStable
	if plan.gitignoreCleanup != nil {
		if err := plan.gitignoreCleanup.validate(); err != nil {
			facts.ManagedIgnore = worktreeremove.InvariantChanged
			return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(".gitignore changed before removal: %w", err)}
		}
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run `bd worktree remove` to build a fresh plan against the current marker state
  2. Check whether another process (git command, CI, sync daemon) rewrote the `.git` marker; stop concurrent modification first
  3. Inspect the marker content (`cat <worktree>/.git`) to confirm it still points at the expected admin directory before retrying
  4. If the marker was legitimately changed, treat it as a new worktree and remove it with a new command instead of forcing the old plan

Example fix

// before: plan built, then `git worktree repair` runs elsewhere
plan := buildRemovalPlan(target)
gitWorktreeRepair() // rewrites .git marker
executeRemoval(plan) // fails: target git marker identity changed
// after: complete repair first, then plan+remove atomically
gitWorktreeRepair()
plan := buildRemovalPlan(target)
executeRemoval(plan)
Defensive patterns

Strategy: retry

Validate before calling

markerPath := filepath.Join(worktreePath, ".git")
cur, err := os.Stat(markerPath)
if err != nil { return err }
if !os.SameFile(cur, pinned.gitMarkerInfo) || cur.Size() != pinned.gitMarkerInfo.Size() {
    return fmt.Errorf(".git marker replaced; rebuild plan")
}

Type guard

func markerUnchanged(current, pinned os.FileInfo) bool {
    return os.SameFile(current, pinned) &&
        current.Mode() == pinned.Mode() &&
        current.Size() == pinned.Size()
}

Try / catch

err := bdWorktreeRemove(path)
if err != nil && strings.Contains(err.Error(), "git marker identity changed") {
    // marker file was rewritten: re-verify and retry with a fresh plan
    err = rebuildPlanAndRemove(path)
}

Prevention

When it happens

Trigger: Calling `bd worktree remove` after the worktree's `.git` marker file was replaced or rewritten between plan and execution — e.g. `git worktree repair`, re-cloning into the same path, a script rewriting the `.git` pointer file, or metadata-touching tools (touch, chmod, backup restore) altering mode/size/mtime.

Common situations: Concurrent `git worktree repair`/`add` from another terminal or agent; container or mount remounting replaced the file; an editor or linter rewrote the `.git` file; running the remove command in a retry loop against a worktree that another process keeps refreshing.

Related errors


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