gastownhall/beads · error
target is no longer clean
Error message
target is no longer clean
What it means
This error aborts a planned bd worktree operation when the target worktree was clean when the plan was built but now has `git status` output, and the plan was not created with force. bd refuses to remove or mutate a worktree that has accumulated uncommitted changes since planning, to avoid destroying work.
Source
Thrown at cmd/bd/worktree_cmd.go:1444
if currentTarget.status != plan.target.status {
if (currentTarget.status == "") != (plan.target.status == "") {
facts.Cleanliness = worktreeremove.InvariantChanged
} else {
facts.Cleanliness = worktreeremove.InvariantStable
}
facts.StatusBytes = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target cleanliness changed")}
}
facts.Cleanliness = worktreeremove.InvariantStable
facts.StatusBytes = worktreeremove.InvariantStable
if currentTarget.statusFingerprint != plan.target.statusFingerprint {
facts.DirtyFileFingerprint = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target changed files changed")}
}
facts.DirtyFileFingerprint = worktreeremove.InvariantStable
if !plan.force && currentTarget.status != "" {
facts.Cleanliness = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target is no longer clean")}
}
if !os.SameFile(currentTarget.pathInfo, plan.target.pathInfo) ||
!samePinnedFileMetadata(currentTarget.pathInfo, plan.target.pathInfo) {
facts.TargetDirectory = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target directory identity changed")}
}
facts.TargetDirectory = worktreeremove.InvariantStable
if !os.SameFile(currentTarget.gitDirInfo, plan.target.gitDirInfo) ||
!samePinnedFileMetadata(currentTarget.gitDirInfo, plan.target.gitDirInfo) {
facts.GitAdminDirectory = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target git directory identity changed")}
}
facts.GitAdminDirectory = worktreeremove.InvariantStable
if !os.SameFile(currentTarget.gitMarkerInfo, plan.target.gitMarkerInfo) ||
!samePinnedFileMetadata(currentTarget.gitMarkerInfo, plan.target.gitMarkerInfo) {
facts.GitMarker = worktreeremove.InvariantChanged
return worktreeRevalidationObservation{facts: facts, err: fmt.Errorf("target git marker identity changed")}
}View on GitHub (pinned to 71377f2769)
Solutions
- Inspect `git -C <worktree> status`, then commit or stash the new changes before retrying the bd operation
- If the changes are disposable artifacts, run `git -C <worktree> clean -fd` (and `git checkout -- .` for tracked edits) to restore cleanliness, then re-run
- Re-run the bd command so the plan reflects the current (dirty) state; a fresh plan for a dirty tree will either proceed appropriately or prompt for force
- If the dirty state is intentional and you accept the risk, retry with the force flag
Example fix
// before: removing a worktree that became dirty
executeRemove(plan) // error: target is no longer clean
// after: stabilize first
run("git", "-C", wt, "stash", "-u") // or commit
executeRemove(rebuildPlan(wt)) // succeeds Defensive patterns
Strategy: validation
Validate before calling
if !plan.Force {
out, _ := exec.Command("git", "-C", wt, "status", "--porcelain").Output()
if len(out) > 0 {
// commit, stash, or clean before calling bd; or require force
}
} Type guard
func cleanOrForced(force bool, wt string) bool {
if force { return true }
out, err := exec.Command("git", "-C", wt, "status", "--porcelain").Output()
return err == nil && len(out) == 0
} Try / catch
err := bdRemoveWorktree(plan)
if err != nil && strings.Contains(err.Error(), "target is no longer clean") {
// surface to the user: uncommitted changes detected; do NOT auto-force
return fmt.Errorf("worktree %s has new uncommitted changes; commit/stash or retry with --force", wt)
} Prevention
- Check `git status --porcelain` for the worktree before any remove-style operation
- Commit or stash work-in-progress before planning worktree removal
- Never blindly pass force; treat this error as a data-loss warning requiring human review
- Pause editors/agents working in the worktree while bd operations run
When it happens
Trigger: Calling the planned worktree remove/operation with plan.force == false when the target's `git status --porcelain` is non-empty at execution time but was empty at plan time — i.e. any new modification, staged change, or untracked file appeared after planning.
Common situations: The developer (or an IDE) started editing files in the worktree after running the bd command; a build or test run wrote untracked artifacts; a background sync pulled in changes; scripted flows where planning happened minutes before execution while the tree was actively developed on.
Related errors
- target HEAD changed from %s to %s
- target directory identity changed
- failed to inspect created worktree cleanliness: %w %s
- created worktree is dirty after checkout; refusing to contin
- worktree not found: %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/c83fc9f62321131a.
Report an issue: GitHub.