{"record":{"id":"7c1831a11f155c86","repo":"gastownhall/beads","slug":"edge-d-s-s-checking-planned-blocking-cycle","errorCode":null,"errorMessage":"edge %d %s->%s: checking planned blocking cycle: %w","messagePattern":"edge (.+?) (.+?)->(.+?): checking planned blocking cycle: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/graph_apply.go","lineNumber":1144,"sourceCode":"\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\n// validateGraphApplyPlannedParentBlockingPaths rejects plans where a planned\n// blocking edge would create a path from a parent to its child. Unlike\n// validateGraphApplyPlannedBlockingCycles, its existing-dep walk follows the\n// full AffectsReadyWork set (blocks, conditional-blocks, parent-child,\n// waits-for) because a parent→child path closed through any ready-affecting\n// dependency is a real ready-work deadlock. The two predicates must stay\n// distinct: narrowing this one would miss real deadlocks, while this broader\n// walk may additionally reject a return path through waits-for.\nfunc validateGraphApplyPlannedParentBlockingPaths(ctx context.Context, tx storage.Transaction, plan *GraphApplyPlan, keyToID map[string]string, parentDepPairs map[string]bool) error {\n\tadj := make(map[string][]string)","sourceCodeStart":1126,"sourceCodeEnd":1162,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/graph_apply.go#L1126-L1162","documentation":"Wrapper error returned when graphApplyHasPath fails (an underlying storage/tx error) while checking whether a planned edge would close a blocking cycle. The %w preserves the underlying cause; the message is context, not the root problem.","triggerScenarios":"bd graph apply plan validation where the dependency lookup inside graphApplyHasPath (e.g. tx.GetDependencyRecords) returns an error for some node reachable during the cycle search.","commonSituations":"Database/transaction errors, corrupted dependency records, storage backend unavailable during apply.","solutions":["Read the wrapped cause at the end of the message and fix that underlying error first","Retry the apply once storage is healthy","If GetDependencyRecords keeps failing, verify database integrity for the involved issues"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := bd.GraphApply(ctx, plan); err != nil {\n  var root error = err\n  for errors.Unwrap(root) != nil { root = errors.Unwrap(root) }\n  log.Printf(\"cycle-check failed, root cause: %v\", root) // handle storage error\n}","preventionTips":["Ensure storage is healthy before running apply","Treat this as a transport/storage failure, not a plan problem","Retry with backoff on transient DB errors"],"tags":["go","error-wrapping","graph-apply","storage"],"backgroundTag":"dependency-cycle-check-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}