gastownhall/beads · error

applyGraph: node %q: parent-child dep %s->%s: %w

Error message

applyGraph: node %q: parent-child dep %s->%s: %w

What it means

Pass 3 of applyGraph inserts the implicit parent-child dependency rows for each node with a parent. If the dependency repository Insert fails (storage error, constraint violation, connection loss), the error is wrapped with this message identifying the node key and child->parent pair, and the whole apply aborts.

Source

Thrown at internal/storage/domain/issue.go:1215

	// 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]
		dep := &types.Dependency{
			IssueID:     childID,
			DependsOnID: parentID,
			Type:        types.DepParentChild,
		}
		if err := u.depRepo.Insert(ctx, dep, actor, DepInsertOpts{UseWispsTable: useWisp}); err != nil {
			return GraphApplyResult{}, fmt.Errorf("applyGraph: node %q: parent-child dep %s->%s: %w", node.Key, childID, parentID, err)
		}
		newSchedulingEdges = append(newSchedulingEdges, [2]string{childID, parentID})
	}

	// Pass 4 — insert explicit edge deps in two stable phases: all additional
	// parent-child edges first, then every other type. Deduplicate against the
	// node parent-child pairs:
	//   - Same pair, parent-child type → skip (pass 3 already inserted it).
	//   - Same pair, different type   → error (conflicting edge over a parent-child link).
	//   - Reverse pair, blocking type → error (creates a parent → child blocking cycle).
	//
	// The blocking-only whole-graph preflight above gives early, edge-specific
	// errors. Repository Insert remains the defensive authority for the broader
	// blocks + conditional-blocks + parent-child scheduling graph.
	for phase := 0; phase < 2; phase++ {
		parentPhase := phase == 0
		for i, edge := range plan.Edges {
			fromID := resolveEdgeRef(edge.FromKey, edge.FromID, keyToID)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped cause (%w) and address the underlying storage error (restart Dolt, check connectivity)
  2. Remove the pre-existing duplicate dependency (bd dep remove child --depends-on parent) and re-apply
  3. Re-run applyGraph with an idempotent/rebuilt plan; the pre-write passes should now dedupe
  4. Check schema version compatibility after upgrading beads (bd migrate/doctor)

Example fix

// before
// applyGraph fails: insert conflicts with existing row
// after
// delete the stale dep first:
//   bd dep remove bd-2 --depends-on bd-1
// then re-run applyGraph with the same plan
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check for an existing parent-child dep to avoid insert conflicts:
for _, n := range plan.Nodes {
    if n.Parent != "" && depExists(n.Key, n.Parent) {
        return fmt.Errorf("dep %s->%s already exists", n.Key, n.Parent)
    }
}

Try / catch

res, err := applyGraph(ctx, plan)
if err != nil {
    var wrapped string = err.Error()
    if strings.Contains(wrapped, "parent-child dep") {
        // inspect %w cause: storage down? duplicate row? retry after cleanup
        log.Printf("graph apply failed on parent-child insert: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: u.depRepo.Insert returns an error while writing a DepParentChild row: DB locked/unreachable, duplicate dependency row already exists, wisps-table flag mismatch, or driver-level failure.

Common situations: Dolt server down mid-apply; leftover parent-child dep from a partially applied earlier plan; permission/schema issues after a beads version upgrade; disk full.

Related errors


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