{"record":{"id":"192a31085dc7f0aa","repo":"gastownhall/beads","slug":"managed-ignore-cleanup-identity-is-absent-or-inval","errorCode":null,"errorMessage":"managed ignore cleanup identity is absent or invalid","messagePattern":"managed ignore cleanup identity is absent or invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/worktreeremove/policy.go","lineNumber":198,"sourceCode":"\tdefault:\n\t\treturn Plan{}, fmt.Errorf(\"registered worktree target is absent or invalid\")\n\t}\n\tif facts.Registration != Present || facts.RegisteredPath == \"\" ||\n\t\tfacts.TargetDir != Present || facts.GitAdminDir != Present ||\n\t\tfacts.GitMarker != Present || facts.Head != Present {\n\t\treturn Plan{}, fmt.Errorf(\"registered worktree target is absent or invalid\")\n\t}\n\tif facts.CommonDir != Matched {\n\t\treturn Plan{}, fmt.Errorf(\"target common git directory does not match repository\")\n\t}\n\tif facts.Status != Clean && facts.Status != Dirty {\n\t\treturn Plan{}, fmt.Errorf(\"worktree cleanliness observation is absent or invalid\")\n\t}\n\tif facts.ManagedIgnore != IgnoreAbsent && facts.ManagedIgnore != IgnoreManaged {\n\t\treturn Plan{}, fmt.Errorf(\"managed ignore observation is absent or invalid\")\n\t}\n\tif (facts.ManagedIgnore == IgnoreManaged) != (facts.ManagedIgnoreEntry != \"\") {\n\t\treturn Plan{}, fmt.Errorf(\"managed ignore cleanup identity is absent or invalid\")\n\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 {","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/worktreeremove/policy.go#L180-L216","documentation":"Prepare rejects the request because ManagedIgnore and ManagedIgnoreEntry are inconsistent: IgnoreManaged is set without a non-empty entry, or an entry string is present while ManagedIgnore says IgnoreAbsent. The cleanup identity must pair with its observation so the plan can authorize exactly one ignore-file edit.","triggerScenarios":"Calling Prepare with ManagedIgnore: IgnoreManaged but empty ManagedIgnoreEntry, or a non-empty ManagedIgnoreEntry together with IgnoreAbsent.","commonSituations":"Adapters that record the entry string but forget to flip ManagedIgnore; tests that set only one half of the pair; refactors that renamed the entry field.","solutions":["Set ManagedIgnoreEntry to the exact ignore-file entry when ManagedIgnore is IgnoreManaged","Use IgnoreAbsent with an empty ManagedIgnoreEntry when there is no managed entry","Keep the pair set together from a single observation point in the adapter"],"exampleFix":"// before\nfacts.ManagedIgnore = worktreeremove.IgnoreManaged // entry not set\n// after\nfacts.ManagedIgnore = worktreeremove.IgnoreManaged\nfacts.ManagedIgnoreEntry = \"worktrees/foo/\" // exact entry from ignore file","handlingStrategy":"validation","validationCode":"if (facts.ManagedIgnore == worktreeremove.IgnoreManaged) != (facts.ManagedIgnoreEntry != \"\") {\n\treturn fmt.Errorf(\"managed ignore entry/kind mismatch\")\n}","typeGuard":"func ignorePairConsistent(f worktreeremove.PrepareFacts) bool {\n\treturn (f.ManagedIgnore == worktreeremove.IgnoreManaged) == (f.ManagedIgnoreEntry != \"\")\n}","tryCatchPattern":"plan, err := worktreeremove.Prepare(req, facts)\nif err != nil && strings.Contains(err.Error(), \"managed ignore cleanup identity\") {\n\treturn fmt.Errorf(\"re-observe ignore entry and kind together: %w\", err)\n}","preventionTips":["Set ManagedIgnore and ManagedIgnoreEntry from the same single observation","Use a constructor that takes the entry and derives the kind","Add the consistency invariant to adapter tests"],"tags":["worktree","git","validation"],"backgroundTag":"incomplete-observations","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}