{"record":{"id":"36228c91cadc91e1","repo":"gastownhall/beads","slug":"worktree-removal-approval-is-absent-or-invalid","errorCode":null,"errorMessage":"worktree removal approval is absent or invalid","messagePattern":"worktree removal approval is absent or invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/worktreeremove/policy.go","lineNumber":284,"sourceCode":"\t\tfacts.GitMarkerBytes, facts.CommonDirectory, facts.Head, facts.Cleanliness,\n\t\tfacts.StatusBytes, facts.DirtyFileFingerprint, facts.ManagedIgnore,\n\t} {\n\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","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/worktreeremove/policy.go#L266-L302","documentation":"Revalidate requires a Plan that was actually approved by Prepare. It calls plan.valid(), and if the Plan is the zero value, was never produced by Prepare, or was constructed manually without the internal approval flag, Revalidate refuses it before inspecting any facts. This guards against executing removal based on an unapproved plan.","triggerScenarios":"Calling Revalidate with a zero Plan{}; constructing Plan{mode: ..., targetPath: ...} directly without going through Prepare; passing a Plan from a Prepare call that returned an error (Prepare returns Plan{} on failure).","commonSituations":"Ignoring the error from Prepare and using the returned Plan anyway; serializing/deserializing a Plan so the unexported approval field is lost; writing unit tests that hand-build a Plan struct.","solutions":["Only pass a Plan obtained from a successful Prepare call — check Prepare's error first.","Do not round-trip Plan through JSON/DB; keep it in memory for the same process that created it.","In tests, use the mustPrepare/test helper used by the package's own tests instead of constructing Plan literals."],"exampleFix":"// before\nplan, _ := Prepare(req, facts)\nerr := Revalidate(plan, rfacts)\n\n// after\nplan, err := Prepare(req, facts)\nif err != nil {\n\treturn err\n}\nerr = Revalidate(plan, rfacts)","handlingStrategy":"validation","validationCode":"plan, err := Prepare(req, facts)\nif err != nil {\n\treturn fmt.Errorf(\"prepare failed: %w\", err)\n}\n// plan is guaranteed approved; safe for Revalidate/ClassifyFailure","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always check Prepare's error before using the returned Plan.","Never construct Plan literals; treat it as opaque output of Prepare.","Do not serialize/deserialize Plan across process boundaries."],"tags":["worktree","git","approval","zero-value"],"backgroundTag":"unapproved-plan","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}