gastownhall/beads · error
edge %d %s->%s creates a blocking dependency cycle
Error message
edge %d %s->%s creates a blocking dependency cycle
What it means
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.
Source
Thrown at cmd/bd/graph_apply.go:1134
index int
fromID string
toID string
}
adj := make(map[string][]string)
checks := make([]plannedEdge, 0, len(plan.Edges))
for i, edge := range plan.Edges {
depType := graphApplyDependencyType(edge.Type)
if !graphApplyCycleRelevantDependencyType(depType) {
continue
}
fromID := resolveEdgeRef(edge.FromKey, edge.FromID, keyToID)
toID := resolveEdgeRef(edge.ToKey, edge.ToID, keyToID)
if fromID == "" || toID == "" {
continue
}
if fromID == toID {
return fmt.Errorf("edge %d %s->%s creates a blocking dependency cycle", i, fromID, toID)
}
adj[fromID] = append(adj[fromID], toID)
checks = append(checks, plannedEdge{index: i, fromID: fromID, toID: toID})
}
depCache := make(map[string][]*types.Dependency)
for _, edge := range checks {
hasPath, err := graphApplyHasPath(ctx, tx, adj, depCache, edge.toID, edge.fromID, graphApplyCycleRelevantDependencyType)
if err != nil {
return fmt.Errorf("edge %d %s->%s: checking planned blocking cycle: %w", edge.index, edge.fromID, edge.toID, err)
}
if hasPath {
return fmt.Errorf("edge %d %s->%s creates a blocking dependency cycle", edge.index, edge.fromID, edge.toID)
}
}
return nil
}
View on GitHub (pinned to 71377f2769)
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
Example fix
// before
{"edges":[{"from":"bd-1","to":"bd-1"}]}
// after
{"edges":[{"from":"bd-1","to":"bd-2"}]} Defensive patterns
Strategy: validation
Validate before calling
for i, e := range plan.Edges {
if resolveEdgeRef(e.FromKey, e.FromID, keyToID) == resolveEdgeRef(e.ToKey, e.ToID, keyToID) {
return fmt.Errorf("edge %d is a self-loop (%s)", i, e.FromKey)
}
} Prevention
- 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
When it happens
Trigger: 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".
Common situations: 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.
Related errors
- graph dependency cycle would be created: %s
- node %q: planned blocking dependencies create a path from pa
- applyGraph: node %d (key=%q) has nil Issue
- applyGraph: dependency cycle would be created: %s
- applyGraph: node %q: planned blocking dependencies create a
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/2bebe693aa5a830d.
Report an issue: GitHub.