gastownhall/beads · error

worktree removal approval is absent or invalid

Error message

worktree removal approval is absent or invalid

What it means

Revalidate requires a Plan that was actually approved by Prepare. It calls plan.valid(), and if the Plan is the zero value, was never produced by Prepare, or was constructed manually without the internal approval flag, Revalidate refuses it before inspecting any facts. This guards against executing removal based on an unapproved plan.

Source

Thrown at internal/worktreeremove/policy.go:284

		facts.GitMarkerBytes, facts.CommonDirectory, facts.Head, facts.Cleanliness,
		facts.StatusBytes, facts.DirtyFileFingerprint, facts.ManagedIgnore,
	} {
		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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Only pass a Plan obtained from a successful Prepare call — check Prepare's error first.
  2. Do not round-trip Plan through JSON/DB; keep it in memory for the same process that created it.
  3. In tests, use the mustPrepare/test helper used by the package's own tests instead of constructing Plan literals.

Example fix

// before
plan, _ := Prepare(req, facts)
err := Revalidate(plan, rfacts)

// after
plan, err := Prepare(req, facts)
if err != nil {
	return err
}
err = Revalidate(plan, rfacts)
Defensive patterns

Strategy: validation

Validate before calling

plan, err := Prepare(req, facts)
if err != nil {
	return fmt.Errorf("prepare failed: %w", err)
}
// plan is guaranteed approved; safe for Revalidate/ClassifyFailure

Prevention

When it happens

Trigger: Calling Revalidate with a zero Plan{}; constructing Plan{mode: ..., targetPath: ...} directly without going through Prepare; passing a Plan from a Prepare call that returned an error (Prepare returns Plan{} on failure).

Common situations: Ignoring the error from Prepare and using the returned Plan anyway; serializing/deserializing a Plan so the unexported approval field is lost; writing unit tests that hand-build a Plan struct.

Related errors


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