{"record":{"id":"22baa59f6a262178","repo":"gastownhall/beads","slug":"reparent-s-cannot-be-its-own-parent","errorCode":null,"errorMessage":"reparent: %s cannot be its own parent","messagePattern":"reparent: (.+?) cannot be its own parent","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/dependency.go","lineNumber":385,"sourceCode":"\t\treturn fmt.Errorf(\"remove dep %s -> %s: %w\", sourceID, dependsOnID, err)\n\t}\n\treturn nil\n}\n\nfunc (u *dependencyUseCaseImpl) Reparent(ctx context.Context, childID, newParentID, actor string) error {\n\treturn u.reparent(ctx, childID, newParentID, actor, false)\n}\n\nfunc (u *dependencyUseCaseImpl) ReparentWisp(ctx context.Context, childWispID, newParentID, actor string) error {\n\treturn u.reparent(ctx, childWispID, newParentID, actor, true)\n}\n\nfunc (u *dependencyUseCaseImpl) reparent(ctx context.Context, childID, newParentID, actor string, useWisp bool) error {\n\tif childID == \"\" {\n\t\treturn fmt.Errorf(\"reparent: childID must not be empty\")\n\t}\n\tif childID == newParentID {\n\t\treturn fmt.Errorf(\"reparent: %s cannot be its own parent\", childID)\n\t}\n\n\topts := DepInsertOpts{UseWispsTable: useWisp}\n\tres, err := u.depRepo.ListByIssueIDs(ctx, []string{childID}, DepListOpts{\n\t\tTypes:         []types.DependencyType{types.DepParentChild},\n\t\tDirection:     DepDirectionOut,\n\t\tUseWispsTable: useWisp,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"reparent: list current parent: %w\", err)\n\t}\n\n\t// A child can carry MORE THAN ONE parent-child edge — Create accepts\n\t// CreateRequest.ParentID and an explicit parent-child entry in\n\t// Dependencies in the same request — so this is a set replacement, not a\n\t// swap of one edge. Diffing the whole existing set against the target set\n\t// is the same rule the store-backed backends apply in\n\t// issueops.ApplyParentPatch; that body cannot be called from here because","sourceCodeStart":367,"sourceCodeEnd":403,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/dependency.go#L367-L403","documentation":"Guard error from reparent: the caller asked to make an issue its own parent (childID == newParentID). A self parent-child edge is invalid in the hierarchy model, so the use case rejects it before any lookup or write. This is analogous to the self-dependency guard in add().","triggerScenarios":"Reparent(ctx, id, id, actor) or ReparentWisp(ctx, id, id, actor) — typically a UI/form default where the parent dropdown was pre-filled with the child itself, or a script that computes parentID from the same variable as childID.","commonSituations":"Bulk re-parent scripts mapping issues to 'themselves' as a default fallback; migration code that copies a flat list into parent-child form without excluding self-mappings; user selecting the current issue as its new parent.","solutions":["Compare childID and newParentID at the call site and skip no-op self-reparents before calling.","Fix the parent-selection logic so a child is never offered/chosen as its own parent.","If the intent was 'no change', skip the call entirely rather than passing equal IDs.","Validate parent choice in scripts: reject any mapping where parent == child."],"exampleFix":"// before\nerr := uc.Reparent(ctx, childID, parentID, actor)\n// after\nif childID == parentID {\n    return nil // no-op reparent\n}\nerr := uc.Reparent(ctx, childID, parentID, actor)","handlingStrategy":"validation","validationCode":"if childID == newParentID {\n    return nil // or return a descriptive no-op/validation error\n}\nerr := uc.Reparent(ctx, childID, newParentID, actor)","typeGuard":"func isSelfParent(childID, newParentID string) bool {\n    return childID != \"\" && childID == newParentID\n}","tryCatchPattern":"if isSelfParent(childID, newParentID) {\n    log.Printf(\"skipping self-parent for %s\", childID)\n    return nil\n}\nif err := uc.Reparent(ctx, childID, newParentID, actor); err != nil {\n    return fmt.Errorf(\"reparent rejected: %w\", err)\n}","preventionTips":["Never pre-fill a parent selector with the child's own ID","Sanitize bulk parent mappings: reject parent == child entries","Treat self-reparent as a no-op and skip rather than error in scripts","Mirror the self-dependency rule in any custom UI/form validation"],"tags":["go","validation","reparent","hierarchy","self-reference"],"backgroundTag":"self-parenting-rejected","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}