{"record":{"id":"c83fc9f62321131a","repo":"gastownhall/beads","slug":"target-is-no-longer-clean","errorCode":null,"errorMessage":"target is no longer clean","messagePattern":"target is no longer clean","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/worktree_cmd.go","lineNumber":1444,"sourceCode":"\tif currentTarget.status != plan.target.status {\n\t\tif (currentTarget.status == \"\") != (plan.target.status == \"\") {\n\t\t\tfacts.Cleanliness = worktreeremove.InvariantChanged\n\t\t} else {\n\t\t\tfacts.Cleanliness = worktreeremove.InvariantStable\n\t\t}\n\t\tfacts.StatusBytes = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target cleanliness changed\")}\n\t}\n\tfacts.Cleanliness = worktreeremove.InvariantStable\n\tfacts.StatusBytes = worktreeremove.InvariantStable\n\tif currentTarget.statusFingerprint != plan.target.statusFingerprint {\n\t\tfacts.DirtyFileFingerprint = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target changed files changed\")}\n\t}\n\tfacts.DirtyFileFingerprint = worktreeremove.InvariantStable\n\tif !plan.force && currentTarget.status != \"\" {\n\t\tfacts.Cleanliness = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target is no longer clean\")}\n\t}\n\tif !os.SameFile(currentTarget.pathInfo, plan.target.pathInfo) ||\n\t\t!samePinnedFileMetadata(currentTarget.pathInfo, plan.target.pathInfo) {\n\t\tfacts.TargetDirectory = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target directory identity changed\")}\n\t}\n\tfacts.TargetDirectory = worktreeremove.InvariantStable\n\tif !os.SameFile(currentTarget.gitDirInfo, plan.target.gitDirInfo) ||\n\t\t!samePinnedFileMetadata(currentTarget.gitDirInfo, plan.target.gitDirInfo) {\n\t\tfacts.GitAdminDirectory = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target git directory identity changed\")}\n\t}\n\tfacts.GitAdminDirectory = worktreeremove.InvariantStable\n\tif !os.SameFile(currentTarget.gitMarkerInfo, plan.target.gitMarkerInfo) ||\n\t\t!samePinnedFileMetadata(currentTarget.gitMarkerInfo, plan.target.gitMarkerInfo) {\n\t\tfacts.GitMarker = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target git marker identity changed\")}\n\t}","sourceCodeStart":1426,"sourceCodeEnd":1462,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/worktree_cmd.go#L1426-L1462","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: removing a worktree that became dirty\nexecuteRemove(plan) // error: target is no longer clean\n// after: stabilize first\nrun(\"git\", \"-C\", wt, \"stash\", \"-u\") // or commit\nexecuteRemove(rebuildPlan(wt)) // succeeds","handlingStrategy":"validation","validationCode":"if !plan.Force {\n    out, _ := exec.Command(\"git\", \"-C\", wt, \"status\", \"--porcelain\").Output()\n    if len(out) > 0 {\n        // commit, stash, or clean before calling bd; or require force\n    }\n}","typeGuard":"func cleanOrForced(force bool, wt string) bool {\n    if force { return true }\n    out, err := exec.Command(\"git\", \"-C\", wt, \"status\", \"--porcelain\").Output()\n    return err == nil && len(out) == 0\n}","tryCatchPattern":"err := bdRemoveWorktree(plan)\nif err != nil && strings.Contains(err.Error(), \"target is no longer clean\") {\n    // surface to the user: uncommitted changes detected; do NOT auto-force\n    return fmt.Errorf(\"worktree %s has new uncommitted changes; commit/stash or retry with --force\", wt)\n}","preventionTips":["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"],"tags":["git","worktree","uncommitted-changes","data-loss-prevention","safety-check"],"backgroundTag":"git-worktree-not-clean","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}