gastownhall/beads · error

cannot remove the primary worktree

Error message

cannot remove the primary worktree

What it means

Prepare refuses any removal whose Target is PrimaryWorktree: the main worktree of a repository can never be removed via this policy, because deleting it would destroy the repository's primary checkout. Only registered linked worktrees (Target == RegisteredTarget) are eligible. This is an unconditional policy refusal, independent of the supplied facts.

Source

Thrown at internal/worktreeremove/policy.go:176

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")
	}
	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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Skip the primary worktree: filter it out of any worktree list before building removal requests.
  2. Point the operation at a linked worktree (Target: RegisteredTarget) instead.
  3. If the intent is to remove the whole repository, use a different workflow (delete the repo) — this API will never allow it.
  4. In automation, detect PrimaryWorktree early and return a friendly message instead of calling Prepare.

Example fix

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

// after
if target == worktreeremove.PrimaryWorktree {
    return fmt.Errorf("refusing: cannot remove the primary worktree")
}
plan, err := worktreeremove.Prepare(worktreeremove.Request{
    Target: worktreeremove.RegisteredTarget,
    Mode:   worktreeremove.Normal,
}, facts)
Defensive patterns

Strategy: validation

Validate before calling

if facts.Target == worktreeremove.PrimaryWorktree {
    return fmt.Errorf("%s is the primary worktree; refusing to remove", registeredPath)
}

Type guard

func removable(facts worktreeremove.PrepareFacts) bool {
    return facts.Target == worktreeremove.RegisteredTarget
}

Try / catch

plan, err := worktreeremove.Prepare(req, facts)
if err != nil && strings.Contains(err.Error(), "primary worktree") {
    return fmt.Errorf("cannot remove the primary worktree; choose a linked worktree")
}

Prevention

When it happens

Trigger: Calling Prepare with Request{Target: PrimaryWorktree} in any Mode; a CLI resolving the current directory or configured path to the primary worktree and passing it as the removal target.

Common situations: User runs the remove command while pointed at the repo's root checkout; automation that iterates over all `git worktree list` entries including the first (primary) one; misconfigured script defaulting to the repo root.

Related errors


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