gastownhall/beads · error
applyGraph: edge %d %s->%s duplicates a parent-child relatio
Error message
applyGraph: edge %d %s->%s duplicates a parent-child relationship with dependency type %q
What it means
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.
Source
Thrown at internal/storage/domain/issue.go:1190
}
// Preserve failure-before-write for explicit edges that conflict directly
// with an implicit node parent relationship. Parent-first mutation below is
// for transitive hierarchy visibility, not for deferring structural errors.
for i, edge := range plan.Edges {
fromID := resolveEdgeRef(edge.FromKey, edge.FromID, keyToID)
if fromID == "" {
return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d references undefined from_key %q", i, edge.FromKey)
}
toID := resolveEdgeRef(edge.ToKey, edge.ToID, keyToID)
if toID == "" {
return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d references undefined to_key %q", i, edge.ToKey)
}
depType := edge.Type
if depType == "" {
depType = types.DepBlocks
}
if parentDepPairs[depPairKey(fromID, toID)] && depType != types.DepParentChild {
return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d %s->%s duplicates a parent-child relationship with dependency type %q", i, fromID, toID, depType)
}
if parentDepPairs[depPairKey(toID, fromID)] && cycleRelevantDepType(depType) {
return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d %s->%s creates a blocking reverse of a parent-child relationship", i, fromID, toID)
}
}
// Pass 3 — insert node parent-child deps now that all IDs are known. These
// must be visible before any blocking edge in the same plan so the storage
// hierarchy guard evaluates existing + planned ancestry.
for _, node := range plan.Nodes {
parentID := node.ParentID
if node.ParentKey != "" {
parentID = keyToID[node.ParentKey]
}
if parentID == "" {
continue
}
childID := keyToID[node.Key]View on GitHub (pinned to 71377f2769)
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
Example fix
// before
plan.Nodes = append(plan.Nodes, GraphNode{Key: "bd-2", Parent: "bd-1"})
plan.Edges = append(plan.Edges, GraphEdge{FromKey: "bd-1", ToKey: "bd-2", Type: "blocks"})
// after
plan.Nodes = append(plan.Nodes, GraphNode{Key: "bd-2", Parent: "bd-1"}) // parent-child only Defensive patterns
Strategy: validation
Validate before calling
parentPairs := map[string]bool{}
for _, n := range plan.Nodes {
if n.Parent != "" {
parentPairs[depPairKey(n.Parent, n.Key)] = true
}
}
for _, e := range plan.Edges {
if parentPairs[depPairKey(e.FromKey, e.ToKey)] && e.Type != "parent-child" {
return fmt.Errorf("edge %s->%s duplicates parent-child with type %q", e.FromKey, e.ToKey, e.Type)
}
} Type guard
func edgeConflictsWithParent(e GraphEdge, parentPairs map[string]bool) bool {
return parentPairs[depPairKey(e.FromKey, e.ToKey)] && e.Type != "parent-child"
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- duplicate node key %q
- node %q has empty title
- duplicate explicit id %q (node %q)
- node %q: metadata ref %q references unknown key %q
- node %q: parent key %q not found in plan
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/803be19686a1e741.
Report an issue: GitHub.