{"record":{"id":"274d6e644c2b5906","repo":"gastownhall/beads","slug":"worktree-force-mode-comparison-observations-are-in","errorCode":null,"errorMessage":"worktree force-mode comparison observations are inconsistent","messagePattern":"worktree force-mode comparison observations are inconsistent","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/worktreeremove/policy.go","lineNumber":217,"sourceCode":"\t}\n\tif request.Mode != Force {\n\t\tif facts.Status != Clean {\n\t\t\treturn Plan{}, fmt.Errorf(\"worktree contains modified, untracked, or ignored files\")\n\t\t}\n\t\tif facts.Comparator != ComparatorAvailable && facts.Comparator != ComparatorMissing && facts.Comparator != ComparatorNotRequired {\n\t\t\treturn Plan{}, fmt.Errorf(\"worktree comparison target observation is absent or invalid\")\n\t\t}\n\t\tif facts.Comparator != ComparatorAvailable {\n\t\t\treturn Plan{}, fmt.Errorf(\"cannot verify unpushed commits: no comparison target is available\")\n\t\t}\n\t\tif facts.Containment != Contained && facts.Containment != NotContained && facts.Containment != ContainmentNotRequired {\n\t\t\treturn Plan{}, fmt.Errorf(\"worktree containment observation is absent or invalid\")\n\t\t}\n\t\tif facts.Containment != Contained {\n\t\t\treturn Plan{}, fmt.Errorf(\"worktree HEAD is not contained in the comparison target\")\n\t\t}\n\t} else if facts.Comparator != ComparatorNotRequired || facts.Containment != ContainmentNotRequired {\n\t\treturn Plan{}, fmt.Errorf(\"worktree force-mode comparison observations are inconsistent\")\n\t}\n\tplan := Plan{approved: true, mode: request.Mode, targetPath: facts.RegisteredPath}\n\tif facts.ManagedIgnore == IgnoreManaged {\n\t\tplan.managedIgnoreEntry = facts.ManagedIgnoreEntry\n\t}\n\treturn plan, nil\n}\n\n// InvariantState is the adapter's raw observation of one independently\n// revalidated invariant.\ntype InvariantState uint8\n\nconst (\n\t// InvariantUnknown means the adapter could not complete the observation.\n\tInvariantUnknown InvariantState = iota\n\t// InvariantStable means the current observation matches preparation.\n\tInvariantStable\n\t// InvariantChanged means the current observation differs from preparation.","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/worktreeremove/policy.go#L199-L235","documentation":"Prepare validates that a force-mode worktree removal request carries consistent comparison facts. In force mode the policy skips HEAD-containment comparison entirely, so the facts must explicitly record ComparatorNotRequired AND ContainmentNotRequired. If the caller passes force mode with any comparison observation set (or leaves one of the two 'not required' markers unset), the observations contradict the force contract and Prepare refuses to build an approved Plan.","triggerScenarios":"Calling Prepare with request.Mode == force while facts.Comparator != ComparatorNotRequired or facts.Containment != ContainmentNotRequired — e.g. a caller that populated containment facts from a prior non-force inspection and then flipped the mode to force without clearing them.","commonSituations":"Reusing a populated Facts struct across Prepare calls with different modes; a caller that partially upgraded to force mode but still ran the containment comparator; test scaffolding that fills in realistic containment data for a force request.","solutions":["Set facts.Comparator = ComparatorNotRequired and facts.Containment = ContainmentNotRequired when request.Mode is force.","Build the Facts struct per-request rather than reusing one across mode changes.","If containment genuinely matters, drop force mode and let Prepare run the normal containment check."],"exampleFix":"// before\nfacts := Facts{RegisteredPath: p, ManagedIgnore: IgnoreManaged, Comparator: ComparatorHeadInTarget, Containment: Contained}\nplan, err := Prepare(Request{Mode: ModeForce, WorktreePath: p}, facts)\n\n// after\nfacts := Facts{RegisteredPath: p, ManagedIgnore: IgnoreManaged, Comparator: ComparatorNotRequired, Containment: ContainmentNotRequired}\nplan, err := Prepare(Request{Mode: ModeForce, WorktreePath: p}, facts)","handlingStrategy":"validation","validationCode":"if request.Mode == ModeForce {\n\tif facts.Comparator != ComparatorNotRequired || facts.Containment != ContainmentNotRequired {\n\t\t// fix facts before calling Prepare\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Construct Facts fresh for each Prepare call keyed to the request mode.","Add a unit test asserting force-mode facts use the NotRequired sentinels.","Never carry containment observations over from a prior non-force inspection."],"tags":["worktree","git","policy-validation","force-mode"],"backgroundTag":"inconsistent-observation-facts","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}