gastownhall/beads · error

worktree removal mode is absent or invalid

Error message

worktree removal mode is absent or invalid

What it means

worktreeremove.Prepare rejects a Request whose Mode is neither Normal nor Force (e.g. the zero value of the Mode type). Prepare is a pure validation gate: it refuses to produce a Plan until the caller supplies a valid removal mode. This guards against accidentally building a removal plan from an uninitialized Request.

Source

Thrown at internal/worktreeremove/policy.go:172

	return Mutation{TargetPath: plan.targetPath, Force: plan.mode == Force}
}

// Cleanup returns the exact managed-ignore cleanup approved by Prepare.
func (plan Plan) Cleanup() (Cleanup, bool) {
	if plan.managedIgnoreEntry == "" {
		return Cleanup{}, false
	}
	return Cleanup{Entry: plan.managedIgnoreEntry}, true
}

func (plan Plan) valid() bool {
	return plan.approved && (plan.mode == Normal || plan.mode == Force) && plan.targetPath != ""
}

// Prepare validates the initial observations and returns an approved plan.
func Prepare(request Request, facts PrepareFacts) (Plan, error) {
	if request.Mode != Normal && request.Mode != Force {
		return Plan{}, fmt.Errorf("worktree removal mode is absent or invalid")
	}
	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")
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set Request.Mode explicitly to worktreeremove.Normal or worktreeremove.Force before calling Prepare.
  2. If Mode comes from user input, validate/map the string to a valid Mode constant first and surface a usage error.
  3. Check that you are not passing a zero-value Request literal; fill in Target and other facts too.
  4. If a new Mode was added, update Prepare's acceptance check to include it.

Example fix

// before
plan, err := worktreeremove.Prepare(worktreeremove.Request{Target: worktreeremove.RegisteredTarget}, facts)

// after
plan, err := worktreeremove.Prepare(worktreeremove.Request{
    Target: worktreeremove.RegisteredTarget,
    Mode:   worktreeremove.Normal,
}, facts)
Defensive patterns

Strategy: validation

Validate before calling

if req.Mode != worktreeremove.Normal && req.Mode != worktreeremove.Force {
    return fmt.Errorf("mode must be Normal or Force before calling Prepare")
}
plan, err := worktreeremove.Prepare(req, facts)

Type guard

func validMode(m worktreeremove.Mode) bool {
    return m == worktreeremove.Normal || m == worktreeremove.Force
}

Try / catch

plan, err := worktreeremove.Prepare(req, facts)
if err != nil && strings.Contains(err.Error(), "mode is absent or invalid") {
    return fmt.Errorf("specify --mode=normal or --mode=force")
}

Prevention

When it happens

Trigger: Calling Prepare with a zero-value Request{} or with Request{Mode: <unset/unknown mode constant>}; constructing the Request programmatically and forgetting to set Mode.

Common situations: Struct literal missing the Mode field; deserializing a request from JSON/flags where Mode was not provided; a refactor adding a new mode value not yet accepted by policy.

Related errors


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