{"record":{"id":"90098deb650d57aa","repo":"gastownhall/beads","slug":"worktree-changed-before-removal","errorCode":null,"errorMessage":"worktree changed before removal","messagePattern":"worktree changed before removal","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/worktreeremove/policy.go","lineNumber":287,"sourceCode":"\t\tif state != InvariantStable {\n\t\t\treturn false\n\t\t}\n\t}\n\tif plan.mode == Normal {\n\t\treturn facts.Comparator == InvariantStable && facts.Containment == InvariantStable\n\t}\n\treturn (facts.Comparator == InvariantStable || facts.Comparator == InvariantNotRequired) &&\n\t\t(facts.Containment == InvariantStable || facts.Containment == InvariantNotRequired)\n}\n\n// Revalidate refuses every unknown or changed invariant observed after\n// Prepare. A zero or otherwise unapproved plan is always refused.\nfunc Revalidate(plan Plan, facts RevalidationFacts) error {\n\tif !plan.valid() {\n\t\treturn fmt.Errorf(\"worktree removal approval is absent or invalid\")\n\t}\n\tif !revalidationValid(plan, facts) {\n\t\treturn fmt.Errorf(\"worktree changed before removal\")\n\t}\n\treturn nil\n}\n\n// RevalidationResult records whether reinspection completed without an\n// adapter-side diagnostic after a failed removal.\ntype RevalidationResult uint8\n\nconst (\n\t// RevalidationResultUnknown means reinspection did not report a result.\n\tRevalidationResultUnknown RevalidationResult = iota\n\t// RevalidationPassed means reinspection completed without a diagnostic.\n\tRevalidationPassed\n\t// RevalidationFailed means reinspection produced an adapter diagnostic.\n\tRevalidationFailed\n)\n\n// FailureFacts are raw post-failure observations. Registry and target path","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/worktreeremove/policy.go#L269-L305","documentation":"Revalidate re-checks every invariant that Prepare observed at approval time. If revalidationValid(plan, facts) fails — the registered path, HEAD, containment, ignore state, or any other recorded fact no longer matches what Prepare approved — the worktree is considered to have changed since approval and the removal is refused to avoid deleting the wrong or mutated state.","triggerScenarios":"Any mutation between Prepare and Revalidate: the worktree's HEAD moved, the branch was rebased/checked out, the path was re-registered or unregistered, the managed ignore entry changed, or the caller passes RevalidationFacts captured from a different worktree than the Plan was prepared for.","commonSituations":"Long gaps between approval and removal while other git operations run; concurrent agents operating on the same worktree; passing facts from a second inspection run with slightly different values (e.g. new commit hash after a pull).","solutions":["Minimize the window between Prepare and Revalidate/remove; redo Prepare if anything may have changed.","Capture RevalidationFacts from the same targetPath the Plan was approved for.","Inspect which fact diverged (HEAD, registration, ignore entry) and decide whether to re-run the full Prepare flow for the new state."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if err := Revalidate(plan, freshFacts); err != nil {\n\t// state changed — re-run Prepare for the new state instead of removing\n\treturn err\n}","typeGuard":null,"tryCatchPattern":"if err := Revalidate(plan, facts); err != nil {\n\tif strings.Contains(err.Error(), \"changed before removal\") {\n\t\t// redo Prepare on the current state\n\t}\n\treturn err\n}","preventionTips":["Keep the Prepare→Revalidate→remove sequence tight with no intervening git operations.","Capture RevalidationFacts against the same targetPath the Plan was approved for.","Serialize removal per worktree to avoid concurrent mutations."],"tags":["worktree","git","tocs","invariant-changed"],"backgroundTag":"state-changed-since-validation","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}