{"record":{"id":"803be19686a1e741","repo":"gastownhall/beads","slug":"applygraph-edge-d-s-s-duplicates-a-parent-chi","errorCode":null,"errorMessage":"applyGraph: edge %d %s->%s duplicates a parent-child relationship with dependency type %q","messagePattern":"applyGraph: edge (.+?) (.+?)->(.+?) duplicates a parent-child relationship with dependency type %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/issue.go","lineNumber":1190,"sourceCode":"\t}\n\t// Preserve failure-before-write for explicit edges that conflict directly\n\t// with an implicit node parent relationship. Parent-first mutation below is\n\t// for transitive hierarchy visibility, not for deferring structural errors.\n\tfor i, edge := range plan.Edges {\n\t\tfromID := resolveEdgeRef(edge.FromKey, edge.FromID, keyToID)\n\t\tif fromID == \"\" {\n\t\t\treturn GraphApplyResult{}, fmt.Errorf(\"applyGraph: edge %d references undefined from_key %q\", i, edge.FromKey)\n\t\t}\n\t\ttoID := resolveEdgeRef(edge.ToKey, edge.ToID, keyToID)\n\t\tif toID == \"\" {\n\t\t\treturn GraphApplyResult{}, fmt.Errorf(\"applyGraph: edge %d references undefined to_key %q\", i, edge.ToKey)\n\t\t}\n\t\tdepType := edge.Type\n\t\tif depType == \"\" {\n\t\t\tdepType = types.DepBlocks\n\t\t}\n\t\tif parentDepPairs[depPairKey(fromID, toID)] && depType != types.DepParentChild {\n\t\t\treturn GraphApplyResult{}, fmt.Errorf(\"applyGraph: edge %d %s->%s duplicates a parent-child relationship with dependency type %q\", i, fromID, toID, depType)\n\t\t}\n\t\tif parentDepPairs[depPairKey(toID, fromID)] && cycleRelevantDepType(depType) {\n\t\t\treturn GraphApplyResult{}, fmt.Errorf(\"applyGraph: edge %d %s->%s creates a blocking reverse of a parent-child relationship\", i, fromID, toID)\n\t\t}\n\t}\n\n\t// Pass 3 — insert node parent-child deps now that all IDs are known. These\n\t// must be visible before any blocking edge in the same plan so the storage\n\t// hierarchy guard evaluates existing + planned ancestry.\n\tfor _, node := range plan.Nodes {\n\t\tparentID := node.ParentID\n\t\tif node.ParentKey != \"\" {\n\t\t\tparentID = keyToID[node.ParentKey]\n\t\t}\n\t\tif parentID == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tchildID := keyToID[node.Key]","sourceCodeStart":1172,"sourceCodeEnd":1208,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/issue.go#L1172-L1208","documentation":"applyGraph computes the set of parent-child pairs implied by the plan's node hierarchy (parentDepPairs). If an explicit edge duplicates one of those pairs (same from->to direction) but declares a non-parent-child dependency type (e.g. blocks), the apply aborts because two different relationship semantics for one pair are ambiguous and would corrupt hierarchy/scheduling invariants.","triggerScenarios":"Plan has a node with Parent=X (creating a parent-child dep X->node) plus an explicit edge X->node typed \"blocks\" (or any non-parent-child type), detected in the pre-write pass.","commonSituations":"Mixing hand-written edges with node.Parent fields generated by tooling; migrating an old beads DB where an issue was both a parent and a blocker; two clients produced conflicting plans for the same pair.","solutions":["Remove the redundant explicit edge and rely on node.Parent to express the relationship","Change the explicit edge's Type to \"parent-child\" (it will be skipped as a duplicate)","Drop node.Parent if the relationship really is blocks, not hierarchy","Rebuild the plan from current DB state with bd to avoid stale duplicates"],"exampleFix":"// before\nplan.Nodes = append(plan.Nodes, GraphNode{Key: \"bd-2\", Parent: \"bd-1\"})\nplan.Edges = append(plan.Edges, GraphEdge{FromKey: \"bd-1\", ToKey: \"bd-2\", Type: \"blocks\"})\n// after\nplan.Nodes = append(plan.Nodes, GraphNode{Key: \"bd-2\", Parent: \"bd-1\"}) // parent-child only","handlingStrategy":"validation","validationCode":"parentPairs := map[string]bool{}\nfor _, n := range plan.Nodes {\n    if n.Parent != \"\" {\n        parentPairs[depPairKey(n.Parent, n.Key)] = true\n    }\n}\nfor _, e := range plan.Edges {\n    if parentPairs[depPairKey(e.FromKey, e.ToKey)] && e.Type != \"parent-child\" {\n        return fmt.Errorf(\"edge %s->%s duplicates parent-child with type %q\", e.FromKey, e.ToKey, e.Type)\n    }\n}","typeGuard":"func edgeConflictsWithParent(e GraphEdge, parentPairs map[string]bool) bool {\n    return parentPairs[depPairKey(e.FromKey, e.ToKey)] && e.Type != \"parent-child\"\n}","tryCatchPattern":null,"preventionTips":["Express hierarchy only via node.Parent; never add sibling explicit edges for the same pair","Deduplicate edges against parent pairs during plan generation","When importing graphs, normalize pair direction and type first","One source of truth per issue pair"],"tags":["graph-apply","parent-child","validation"],"backgroundTag":"conflicting-dependency-type","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}