{"record":{"id":"36758e308aa466a8","repo":"gastownhall/beads","slug":"edge-d-s-s-creates-a-blocking-reverse-of-a-par","errorCode":null,"errorMessage":"edge %d %s->%s creates a blocking reverse of a parent-child relationship","messagePattern":"edge (.+?) (.+?)->(.+?) creates a blocking reverse of a parent-child relationship","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/graph_apply.go","lineNumber":1005,"sourceCode":"\t\t}\n\n\t\tparentDepPairs := graphApplyParentDepPairs(plan.Nodes, keyToID)\n\t\tnewSchedulingEdges := make([][2]string, 0, len(plan.Nodes)+len(plan.Edges))\n\t\tif err := validateGraphApplyPlannedParentBlockingPaths(ctx, tx, plan, keyToID, parentDepPairs); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := validateGraphApplyPlannedBlockingCycles(ctx, tx, plan, keyToID); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor i, edge := range plan.Edges {\n\t\t\tfromID := resolveEdgeRef(edge.FromKey, edge.FromID, keyToID)\n\t\t\ttoID := resolveEdgeRef(edge.ToKey, edge.ToID, keyToID)\n\t\t\tdepType := graphApplyDependencyType(edge.Type)\n\t\t\tif parentDepPairs[graphApplyDepPairKey(fromID, toID)] && depType != types.DepParentChild {\n\t\t\t\treturn fmt.Errorf(\"edge %d %s->%s duplicates a parent-child relationship with dependency type %q\", i, fromID, toID, depType)\n\t\t\t}\n\t\t\tif parentDepPairs[graphApplyDepPairKey(toID, fromID)] && graphApplyCycleRelevantDependencyType(depType) {\n\t\t\t\treturn fmt.Errorf(\"edge %d %s->%s creates a blocking reverse of a parent-child relationship\", i, fromID, toID)\n\t\t\t}\n\t\t}\n\n\t\t// Add node parent-child dependencies first. The explicit and inline\n\t\t// dependency sources below are also processed parent-first, so every\n\t\t// blocking edge sees the plan's full hierarchy in storage.\n\t\tfor i, node := range plan.Nodes {\n\t\t\tparentKey := node.effectiveParentKey()\n\t\t\tparentID := node.ParentID\n\t\t\tif parentKey != \"\" {\n\t\t\t\tparentID = keyToID[parentKey]\n\t\t\t}\n\t\t\tif parentID != \"\" {\n\t\t\t\tdep := &types.Dependency{\n\t\t\t\t\tIssueID:     issues[i].ID,\n\t\t\t\t\tDependsOnID: parentID,\n\t\t\t\t\tType:        types.DepParentChild,\n\t\t\t\t}","sourceCodeStart":987,"sourceCodeEnd":1023,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/graph_apply.go#L987-L1023","documentation":"The plan has an edge in the reverse direction of an existing parent-child relationship, and the edge's dependency type is cycle-relevant (blocking). A blocking reverse of a parent-child edge would create a cycle in the blocking graph, so apply rejects it.","triggerScenarios":"plan.Edges includes toID->fromID where fromID is already the parent of toID (pair present reversed in parentDepPairs) and graphApplyCycleRelevantDependencyType(depType) is true (e.g. blocks).","commonSituations":"Generators emitting both hierarchy and blocking edges without cycle checks; merging graphs where one direction is parent-child and the reverse is a blocking link; manually inverted edge endpoints.","solutions":["Remove the reverse blocking edge; parent-child already encodes the relationship.","Swap edge endpoints if the blocking direction was mistakenly inverted — but only if it does not duplicate the parent-child pair.","Add cycle detection in the plan generator before emitting blocking edges.","Run `bd graph validate` (if available) on the plan before apply."],"exampleFix":"// before\nnode: {\"key\": \"bd-1\", \"parents\": [\"bd-2\"]}\nedge: {\"from\": \"bd-2\", \"to\": \"bd-1\", \"type\": \"blocks\"}\n// after\nnode: {\"key\": \"bd-1\", \"parents\": [\"bd-2\"]}","handlingStrategy":"validation","validationCode":"pairs := map[[2]string]bool{}\nfor _, n := range plan.Nodes {\n  for _, p := range n.Parents { pairs[[2]string{n.Key, p}] = true }\n}\ncycleRelevant := map[string]bool{\"blocks\": true}\nfor _, e := range plan.Edges {\n  if pairs[[2]string{e.To, e.From}] && cycleRelevant[e.Type] {\n    return fmt.Errorf(\"edge %s->%s is a blocking reverse of parent-child\", e.From, e.To)\n  }\n}","typeGuard":"func isBlockingReverse(e Edge, pairs map[[2]string]bool) bool {\n  return pairs[[2]string{e.To, e.From}] && cycleRelevant[e.Type]\n}","tryCatchPattern":"if err := bd.GraphApply(ctx, plan); err != nil {\n  if strings.Contains(err.Error(), \"blocking reverse of a parent-child\") {\n    plan = dropReverseBlockingEdges(plan); return bd.GraphApply(ctx, plan)\n  }\n}","preventionTips":["Run cycle detection on blocking edges before apply.","Never emit blocking edges opposite to a parent-child pair.","Validate merged plans from multiple sources for direction conflicts."],"tags":["cli","graph-apply","edges","cycle","dependencies"],"backgroundTag":"dependency-cycle","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}