{"record":{"id":"2bebe693aa5a830d","repo":"gastownhall/beads","slug":"edge-d-s-s-creates-a-blocking-dependency-cycle","errorCode":null,"errorMessage":"edge %d %s->%s creates a blocking dependency cycle","messagePattern":"edge (.+?) (.+?)->(.+?) creates a blocking dependency cycle","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/graph_apply.go","lineNumber":1134,"sourceCode":"\t\tindex  int\n\t\tfromID string\n\t\ttoID   string\n\t}\n\n\tadj := make(map[string][]string)\n\tchecks := make([]plannedEdge, 0, len(plan.Edges))\n\tfor i, edge := range plan.Edges {\n\t\tdepType := graphApplyDependencyType(edge.Type)\n\t\tif !graphApplyCycleRelevantDependencyType(depType) {\n\t\t\tcontinue\n\t\t}\n\t\tfromID := resolveEdgeRef(edge.FromKey, edge.FromID, keyToID)\n\t\ttoID := resolveEdgeRef(edge.ToKey, edge.ToID, keyToID)\n\t\tif fromID == \"\" || toID == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif fromID == toID {\n\t\t\treturn fmt.Errorf(\"edge %d %s->%s creates a blocking dependency cycle\", i, fromID, toID)\n\t\t}\n\t\tadj[fromID] = append(adj[fromID], toID)\n\t\tchecks = append(checks, plannedEdge{index: i, fromID: fromID, toID: toID})\n\t}\n\n\tdepCache := make(map[string][]*types.Dependency)\n\tfor _, edge := range checks {\n\t\thasPath, err := graphApplyHasPath(ctx, tx, adj, depCache, edge.toID, edge.fromID, graphApplyCycleRelevantDependencyType)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"edge %d %s->%s: checking planned blocking cycle: %w\", edge.index, edge.fromID, edge.toID, err)\n\t\t}\n\t\tif hasPath {\n\t\t\treturn fmt.Errorf(\"edge %d %s->%s creates a blocking dependency cycle\", edge.index, edge.fromID, edge.toID)\n\t\t}\n\t}\n\treturn nil\n}\n","sourceCodeStart":1116,"sourceCodeEnd":1152,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/graph_apply.go#L1116-L1152","documentation":"Returned during `bd graph apply` plan validation when an edge in the plan has an identical FromID and ToID — a self-loop, which is the trivially blocking dependency cycle. The validator rejects it before touching the database because an issue cannot block itself.","triggerScenarios":"Calling bd graph apply with a dependency edge whose FromKey/ToKey (or FromID/ToID) resolve to the same issue, e.g. JSON input where \"from\":\"bd-1\",\"to\":\"bd-1\".","commonSituations":"Programmatic plan generation with an off-by-one or aliasing bug reusing the same variable for both endpoints; key-to-ID resolution collapsing two intended keys to the same ID; hand-edited JSON.","solutions":["Inspect the offending edge (index printed in the message) and change one endpoint to the intended different issue key","Add a pre-submit check that skips or errors on edge.FromKey == edge.ToKey","If both keys look different but resolve to the same ID, verify the keyToID mapping for duplicate/aliased keys"],"exampleFix":"// before\n{\"edges\":[{\"from\":\"bd-1\",\"to\":\"bd-1\"}]}\n// after\n{\"edges\":[{\"from\":\"bd-1\",\"to\":\"bd-2\"}]}","handlingStrategy":"validation","validationCode":"for i, e := range plan.Edges {\n  if resolveEdgeRef(e.FromKey, e.FromID, keyToID) == resolveEdgeRef(e.ToKey, e.ToID, keyToID) {\n    return fmt.Errorf(\"edge %d is a self-loop (%s)\", i, e.FromKey)\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate every edge has distinct from/to keys before submitting a plan","Build plans programmatically from typed issue objects, not string concat","Test plan generators with self-edge cases"],"tags":["go","dependency-cycle","graph-apply","validation"],"backgroundTag":"dependency-cycle-detected","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}