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 pathView on GitHub (pinned to 71377f2769)
Solutions
- Minimize the window between Prepare and Revalidate/remove; redo Prepare if anything may have changed.
- Capture RevalidationFacts from the same targetPath the Plan was approved for.
- 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
- Keep the Prepare→Revalidate→remove sequence tight with no intervening git operations.
- Capture RevalidationFacts against the same targetPath the Plan was approved for.
- Serialize removal per worktree to avoid concurrent mutations.
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
- failed to inspect created worktree cleanliness: %w %s
- created worktree is dirty after checkout; refusing to contin
- worktree not found: %s
- failed to read git worktree registry: %w
- git worktree registry is empty
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/90098deb650d57aa.
Report an issue: GitHub.