gastownhall/beads · error
applyGraph: node %q: planned blocking dependencies create a
Error message
applyGraph: node %q: planned blocking dependencies create a path from parent %q to child %q
What it means
applyGraph's planned-blocking-path validation detected that the plan's blocking dependencies create a ready path from a parent issue to its own child — meaning the parent could be considered unblocked/ready only after its child, inverting the intended hierarchy ordering. The apply rejects this node's parent/child structure combined with the planned blocking edges.
Source
Thrown at internal/storage/domain/issue.go:1433
adj[fromID] = append(adj[fromID], toID)
}
depCache := make(map[string][]*types.Dependency)
for _, node := range plan.Nodes {
parentID := node.ParentID
if node.ParentKey != "" {
parentID = keyToID[node.ParentKey]
}
childID := keyToID[node.Key]
if childID == "" || parentID == "" {
continue
}
hasPath, err := u.graphHasPath(ctx, adj, depCache, parentID, childID, readyPathDepType)
if err != nil {
return err
}
if hasPath {
return fmt.Errorf("applyGraph: node %q: planned blocking dependencies create a path from parent %q to child %q", node.Key, parentID, childID)
}
}
return nil
}
// validatePlannedBlockingCycles rejects planned blocking edges that would close
// a blocking-dependency cycle, evaluated whole-graph before any insert. It
// mirrors embedded validateGraphApplyPlannedBlockingCycles. This early
// preflight is intentionally restricted to blocking edges; repository Insert
// subsequently enforces the combined scheduling graph for every stored edge.
func (u *issueUseCaseImpl) validatePlannedBlockingCycles(
ctx context.Context,
plan GraphPlan,
keyToID map[string]string,
) error {
type plannedEdge struct {
index int
fromID stringView on GitHub (pinned to 71377f2769)
Solutions
- Read the message to identify the node and the parent/child IDs on the offending path.
- Remove or reverse the blocking edge that links parent back toward child.
- Restructure the plan so parents never depend (transitively) on their children.
- Re-run apply after the hierarchy and blocking edges are consistent.
Example fix
// before: parent blocked by its own child
nodes:
- key: parent
deps: [{target: child, type: blocks}]
- key: child
parent: parent
// after: child blocks on parent context, not the reverse
nodes:
- key: parent
- key: child
parent: parent
deps: [{target: parent, type: blocks}] Defensive patterns
Strategy: validation
Validate before calling
// Pre-check: no blocking path may go from any parent to its child
for _, n := range plan.Nodes {
if n.Parent == "" { continue }
if reachable(adj(plan.Edges, readyPathDepType), n.Key /*parent*/, n.Parent /*child*/) {
return fmt.Errorf("node %s: blocking path parent->child", n.Key)
}
} Try / catch
if err := bd.GraphApply(ctx, plan); err != nil {
if strings.Contains(err.Error(), "path from parent") {
return fmt.Errorf("restructure hierarchy: %w", err)
}
return err
} Prevention
- Never add blocking edges whose direction points from a parent back into its subtree
- Draw the hierarchy and edge directions before authoring plans
- Validate parent/child orientation in generated plans
When it happens
Trigger: A node has a parent-child relationship (parentID -> childID) while the planned blocking edges (filtered to readyPathDepType) produce a directed path parent -> child, so the parent transitively depends on its own descendant.
Common situations: Adding a blocking edge from a parent to a subtask that the subtask's subtree blocks back; plans where parent/child roles were accidentally swapped; composing a subgraph plan onto an existing tree where cross edges close the path.
Related errors
- db: DependencySQLRepository.ValidateBlockingHierarchy: dep m
- edge %d %s->%s creates a blocking dependency cycle
- node %q: planned blocking dependencies create a path from pa
- reparent: %s cannot be its own parent
- applyGraph: dependency cycle would be created: %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/7b53c54b93e287c6.
Report an issue: GitHub.