gastownhall/beads · error

worktree changed before removal

Error message

worktree changed before removal

What it means

Revalidate re-checks every invariant that Prepare observed at approval time. If revalidationValid(plan, facts) fails — the registered path, HEAD, containment, ignore state, or any other recorded fact no longer matches what Prepare approved — the worktree is considered to have changed since approval and the removal is refused to avoid deleting the wrong or mutated state.

Source

Thrown at internal/worktreeremove/policy.go:287

		if state != InvariantStable {
			return false
		}
	}
	if plan.mode == Normal {
		return facts.Comparator == InvariantStable && facts.Containment == InvariantStable
	}
	return (facts.Comparator == InvariantStable || facts.Comparator == InvariantNotRequired) &&
		(facts.Containment == InvariantStable || facts.Containment == InvariantNotRequired)
}

// Revalidate refuses every unknown or changed invariant observed after
// Prepare. A zero or otherwise unapproved plan is always refused.
func Revalidate(plan Plan, facts RevalidationFacts) error {
	if !plan.valid() {
		return fmt.Errorf("worktree removal approval is absent or invalid")
	}
	if !revalidationValid(plan, facts) {
		return fmt.Errorf("worktree changed before removal")
	}
	return nil
}

// RevalidationResult records whether reinspection completed without an
// adapter-side diagnostic after a failed removal.
type RevalidationResult uint8

const (
	// RevalidationResultUnknown means reinspection did not report a result.
	RevalidationResultUnknown RevalidationResult = iota
	// RevalidationPassed means reinspection completed without a diagnostic.
	RevalidationPassed
	// RevalidationFailed means reinspection produced an adapter diagnostic.
	RevalidationFailed
)

// FailureFacts are raw post-failure observations. Registry and target path

View on GitHub (pinned to 71377f2769)

Solutions

  1. Minimize the window between Prepare and Revalidate/remove; redo Prepare if anything may have changed.
  2. Capture RevalidationFacts from the same targetPath the Plan was approved for.
  3. Inspect which fact diverged (HEAD, registration, ignore entry) and decide whether to re-run the full Prepare flow for the new state.
Defensive patterns

Strategy: validation

Validate before calling

if err := Revalidate(plan, freshFacts); err != nil {
	// state changed — re-run Prepare for the new state instead of removing
	return err
}

Try / catch

if err := Revalidate(plan, facts); err != nil {
	if strings.Contains(err.Error(), "changed before removal") {
		// redo Prepare on the current state
	}
	return err
}

Prevention

When it happens

Trigger: Any mutation between Prepare and Revalidate: the worktree's HEAD moved, the branch was rebased/checked out, the path was re-registered or unregistered, the managed ignore entry changed, or the caller passes RevalidationFacts captured from a different worktree than the Plan was prepared for.

Common situations: Long gaps between approval and removal while other git operations run; concurrent agents operating on the same worktree; passing facts from a second inspection run with slightly different values (e.g. new commit hash after a pull).

Related errors


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