gastownhall/beads · error

node %q: adding dep to %q: %w

Error message

node %q: adding dep to %q: %w

What it means

This wraps a storage failure from tx.AddDependency while inserting an inline node dependency (the dep already constructed successfully). The apply transaction is rolled back and the node key plus dependency target are included to help locate the offending relation.

Source

Thrown at cmd/bd/graph_apply.go:1077

				}
			}

			// Add per-node inline dependencies in stable order for this phase.
			for i, node := range plan.Nodes {
				for _, dep := range node.Deps {
					depType := types.DependencyType(dep.Type)
					if depType == "" {
						depType = types.DepBlocks
					}
					if (depType == types.DepParentChild) != parentPhase {
						continue
					}
					d, err := types.NewGraphNodeDependency(issues[i].ID, depType, dep.Target, keyToID)
					if err != nil {
						return fmt.Errorf("node %q: %w", node.Key, err)
					}
					if err := tx.AddDependency(ctx, d, actor); err != nil {
						return fmt.Errorf("node %q: adding dep to %q: %w", node.Key, dep.Target, err)
					}
					if graphApplySchedulingDependencyType(d.Type) {
						newSchedulingEdges = append(newSchedulingEdges, [2]string{d.IssueID, d.DependsOnID})
					}
				}
			}
		}
		if cyclePath, err := tx.CycleThroughEdges(ctx, newSchedulingEdges); err != nil {
			return fmt.Errorf("final graph cycle check: %w", err)
		} else if cyclePath != "" {
			return fmt.Errorf("graph dependency cycle would be created: %s", cyclePath)
		}

		// Apply deferred assignees.
		for i, assignee := range pendingAssignees {
			updates := map[string]interface{}{
				"assignee": assignee,
			}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped underlying error for the specific storage cause
  2. Confirm the target issue exists right before applying (bd show <target>)
  3. Avoid concurrent writers to the same database during graph apply
  4. Deduplicate deps in the plan to prevent duplicate-dependency rejections

Example fix

// before: two agents applying overlapping plans concurrently
bd graph apply plan.json & bd graph apply plan.json
// after: serialize applies
bd graph apply plan.json && bd graph apply plan2.json
Defensive patterns

Strategy: try-catch

Validate before calling

for _, n := range plan.Nodes {
  for _, d := range n.Deps {
    if !issueExists(keyToID[d.Target]) { return fmt.Errorf("target %s missing", d.Target) }
  }
}

Type guard

func depsPersistable(n Node, db DB, keyToID map[string]string) bool {
  return len(n.Deps) == 0 || every(n.Deps, func(d Dep) bool { return db.HasIssue(keyToID[d.Target]) })
}

Try / catch

if err := bd.GraphApply(ctx, plan); err != nil {
  if strings.Contains(err.Error(), "adding dep to") {
    // apply is atomic; retry once storage is healthy
    return retryGraphApply(ctx, plan)
  }
  return err
}

Prevention

When it happens

Trigger: node.Deps entry passes NewGraphNodeDependency but tx.AddDependency(ctx, d, actor) errors — e.g. target issue missing in storage, constraint violation, duplicate dependency, or backend failure mid-transaction.

Common situations: Concurrent deletion of the target issue between validation and insert, re-applying a partially applied plan causing duplicates, or Dolt storage errors.

Related errors


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