gastownhall/beads · error
worktree force-mode comparison observations are inconsistent
Error message
worktree force-mode comparison observations are inconsistent
What it means
Prepare validates that a force-mode worktree removal request carries consistent comparison facts. In force mode the policy skips HEAD-containment comparison entirely, so the facts must explicitly record ComparatorNotRequired AND ContainmentNotRequired. If the caller passes force mode with any comparison observation set (or leaves one of the two 'not required' markers unset), the observations contradict the force contract and Prepare refuses to build an approved Plan.
Source
Thrown at internal/worktreeremove/policy.go:217
}
if request.Mode != Force {
if facts.Status != Clean {
return Plan{}, fmt.Errorf("worktree contains modified, untracked, or ignored files")
}
if facts.Comparator != ComparatorAvailable && facts.Comparator != ComparatorMissing && facts.Comparator != ComparatorNotRequired {
return Plan{}, fmt.Errorf("worktree comparison target observation is absent or invalid")
}
if facts.Comparator != ComparatorAvailable {
return Plan{}, fmt.Errorf("cannot verify unpushed commits: no comparison target is available")
}
if facts.Containment != Contained && facts.Containment != NotContained && facts.Containment != ContainmentNotRequired {
return Plan{}, fmt.Errorf("worktree containment observation is absent or invalid")
}
if facts.Containment != Contained {
return Plan{}, fmt.Errorf("worktree HEAD is not contained in the comparison target")
}
} else if facts.Comparator != ComparatorNotRequired || facts.Containment != ContainmentNotRequired {
return Plan{}, fmt.Errorf("worktree force-mode comparison observations are inconsistent")
}
plan := Plan{approved: true, mode: request.Mode, targetPath: facts.RegisteredPath}
if facts.ManagedIgnore == IgnoreManaged {
plan.managedIgnoreEntry = facts.ManagedIgnoreEntry
}
return plan, nil
}
// InvariantState is the adapter's raw observation of one independently
// revalidated invariant.
type InvariantState uint8
const (
// InvariantUnknown means the adapter could not complete the observation.
InvariantUnknown InvariantState = iota
// InvariantStable means the current observation matches preparation.
InvariantStable
// InvariantChanged means the current observation differs from preparation.View on GitHub (pinned to 71377f2769)
Solutions
- Set facts.Comparator = ComparatorNotRequired and facts.Containment = ContainmentNotRequired when request.Mode is force.
- Build the Facts struct per-request rather than reusing one across mode changes.
- If containment genuinely matters, drop force mode and let Prepare run the normal containment check.
Example fix
// before
facts := Facts{RegisteredPath: p, ManagedIgnore: IgnoreManaged, Comparator: ComparatorHeadInTarget, Containment: Contained}
plan, err := Prepare(Request{Mode: ModeForce, WorktreePath: p}, facts)
// after
facts := Facts{RegisteredPath: p, ManagedIgnore: IgnoreManaged, Comparator: ComparatorNotRequired, Containment: ContainmentNotRequired}
plan, err := Prepare(Request{Mode: ModeForce, WorktreePath: p}, facts) Defensive patterns
Strategy: validation
Validate before calling
if request.Mode == ModeForce {
if facts.Comparator != ComparatorNotRequired || facts.Containment != ContainmentNotRequired {
// fix facts before calling Prepare
}
} Prevention
- Construct Facts fresh for each Prepare call keyed to the request mode.
- Add a unit test asserting force-mode facts use the NotRequired sentinels.
- Never carry containment observations over from a prior non-force inspection.
When it happens
Trigger: Calling Prepare with request.Mode == force while facts.Comparator != ComparatorNotRequired or facts.Containment != ContainmentNotRequired — e.g. a caller that populated containment facts from a prior non-force inspection and then flipped the mode to force without clearing them.
Common situations: Reusing a populated Facts struct across Prepare calls with different modes; a caller that partially upgraded to force mode but still ran the containment comparator; test scaffolding that fills in realistic containment data for a force request.
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/274d6e644c2b5906.
Report an issue: GitHub.