gastownhall/beads · error

node %q: planned blocking dependencies create a path from pa

Error message

node %q: planned blocking dependencies create a path from parent %q to child %q

What it means

Returned by validateGraphApplyPlannedParentBlockingPaths when a parent's planned blocking dependencies would create a ready-path from the parent to its own child — i.e. the parent could become blocked by (or transitively dependent on) its child, violating parent-child ordering invariants.

Source

Thrown at cmd/bd/graph_apply.go:1202

		adj[fromID] = append(adj[fromID], toID)
	}

	depCache := make(map[string][]*types.Dependency)
	for _, node := range plan.Nodes {
		childID := keyToID[node.Key]
		parentID := node.ParentID
		if parentKey := node.effectiveParentKey(); parentKey != "" {
			parentID = keyToID[parentKey]
		}
		if childID == "" || parentID == "" {
			continue
		}
		hasPath, err := graphApplyHasPath(ctx, tx, adj, depCache, parentID, childID, graphApplyReadyPathDependencyType)
		if err != nil {
			return err
		}
		if hasPath {
			return fmt.Errorf("node %q: planned blocking dependencies create a path from parent %q to child %q", node.Key, parentID, childID)
		}
	}
	return nil
}

// graphApplyHasPath reports whether fromID can reach toID by following the
// in-memory planned adjacency plus existing store dependencies. followExistingDep
// selects which existing dep types the walk traverses, letting callers mirror
// either the early blocking-only preflight or the broader ready-work graph.
func graphApplyHasPath(ctx context.Context, tx storage.Transaction, adj map[string][]string, depCache map[string][]*types.Dependency, fromID, toID string, followExistingDep func(types.DependencyType) bool) (bool, error) {
	seen := make(map[string]bool)
	var visit func(string) (bool, error)
	visit = func(id string) (bool, error) {
		if id == toID {
			return true, nil
		}
		if seen[id] {
			return false, nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Remove or reverse the planned blocking edge that connects the parent's subtree to the child
  2. Detach or re-parent the child before applying the blocking edges
  3. Inspect existing deps on parent and child with bd dep list to find the offending link

Example fix

// before: parent P planned to block issue X, but X (transitively) blocks child C of P
{"nodes":[{"key":"bd-parent"}],"edges":[{"from":"bd-parent","to":"bd-x"}]}
// after: block from the child side or remove the parent->X edge
{"edges":[{"from":"bd-x","to":"bd-parent"}]}
Defensive patterns

Strategy: validation

Validate before calling

// reject blocking edges that cross parent->child subtrees
for _, e := range plan.Edges {
  if isAncestorOrDescendant(tree, e.From, e.To) && blocksReadyPath(e) {
    return fmt.Errorf("edge %s->%s crosses parent/child boundary", e.From, e.To)
  }
}

Prevention

When it happens

Trigger: bd graph apply where node N has parent P, and the planned blocking edges among N/P create a directed path from P to child C along ready-path-relevant dependency types.

Common situations: Adding a blocking dep from a parent to an issue that (transitively) blocks back into the parent's subtree; restructuring hierarchies while stale blocking edges exist between parent and child subtrees.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/4e6c8970fb76c1a7. Report an issue: GitHub.