gastownhall/beads · error

worktree cleanliness observation is absent or invalid

Error message

worktree cleanliness observation is absent or invalid

What it means

Prepare rejects the request because facts.Status is neither Clean nor Dirty. The cleanliness observation was never made or holds an invalid enum value, so the library cannot apply the unclean-worktree safety policy and fails closed.

Source

Thrown at internal/worktreeremove/policy.go:192

	switch facts.Target {
	case PrimaryWorktree:
		return Plan{}, fmt.Errorf("cannot remove the primary worktree")
	case CurrentWorktree:
		return Plan{}, fmt.Errorf("cannot remove the worktree containing the running command")
	case RegisteredTarget:
	default:
		return Plan{}, fmt.Errorf("registered worktree target is absent or invalid")
	}
	if facts.Registration != Present || facts.RegisteredPath == "" ||
		facts.TargetDir != Present || facts.GitAdminDir != Present ||
		facts.GitMarker != Present || facts.Head != Present {
		return Plan{}, fmt.Errorf("registered worktree target is absent or invalid")
	}
	if facts.CommonDir != Matched {
		return Plan{}, fmt.Errorf("target common git directory does not match repository")
	}
	if facts.Status != Clean && facts.Status != Dirty {
		return Plan{}, fmt.Errorf("worktree cleanliness observation is absent or invalid")
	}
	if facts.ManagedIgnore != IgnoreAbsent && facts.ManagedIgnore != IgnoreManaged {
		return Plan{}, fmt.Errorf("managed ignore observation is absent or invalid")
	}
	if (facts.ManagedIgnore == IgnoreManaged) != (facts.ManagedIgnoreEntry != "") {
		return Plan{}, fmt.Errorf("managed ignore cleanup identity is absent or invalid")
	}
	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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Populate facts.Status with Clean or Dirty from an actual git status observation
  2. Fix the adapter so a failed status observation sets a defined value rather than leaving Status zero
  3. In tests, set Status: worktreeremove.Clean (or Dirty) explicitly

Example fix

// before
facts := worktreeremove.PrepareFacts{...} // Status left zero
// after
facts := worktreeremove.PrepareFacts{...}
facts.Status = worktreeremove.Clean // from actual git status observation
Defensive patterns

Strategy: validation

Validate before calling

if facts.Status != worktreeremove.Clean && facts.Status != worktreeremove.Dirty {
	return fmt.Errorf("status not observed for %s", facts.RegisteredPath)
}

Type guard

func statusObserved(s worktreeremove.Status) bool {
	return s == worktreeremove.Clean || s == worktreeremove.Dirty
}

Try / catch

plan, err := worktreeremove.Prepare(req, facts)
if err != nil && strings.Contains(err.Error(), "cleanliness observation is absent") {
	return fmt.Errorf("adapter failed to observe git status: %w", err)
}

Prevention

When it happens

Trigger: Calling Prepare with a zero-value Status field, or an adapter that leaves Status unset after a failed git status observation.

Common situations: Custom adapter code that skips the status check; constructing PrepareFacts in tests without Status; an enum added later and not handled in the adapter.

Related errors


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