gastownhall/beads · error

adding edge %s->%s: %w

Error message

adding edge %s->%s: %w

What it means

This wraps a failure from tx.AddDependencyWithOptions when persisting a graph edge dependency inside the apply transaction. The edge constructed successfully but the storage layer refused the insert — commonly a foreign-key violation (endpoint issue missing in the DB), a duplicate dependency, or a backend error — aborting the whole graph apply.

Source

Thrown at cmd/bd/graph_apply.go:1055

				depType := graphApplyDependencyType(edge.Type)
				if (depType == types.DepParentChild) != parentPhase {
					continue
				}
				if parentDepPairs[graphApplyDepPairKey(fromID, toID)] {
					if depType == types.DepParentChild {
						continue
					}
					return fmt.Errorf("edge %d %s->%s duplicates a parent-child relationship with dependency type %q", i, fromID, toID, depType)
				}
				if parentDepPairs[graphApplyDepPairKey(toID, fromID)] && graphApplyCycleRelevantDependencyType(depType) {
					return fmt.Errorf("edge %d %s->%s creates a blocking reverse of a parent-child relationship", i, fromID, toID)
				}
				dep, err := types.NewGraphEdgeDependency(fromID, toID, depType, edge.Gate, edge.SpawnerKey, edge.SpawnerID, edge.ThreadID, keyToID)
				if err != nil {
					return fmt.Errorf("edge %s->%s: %w", fromID, toID, err)
				}
				if err := tx.AddDependencyWithOptions(ctx, dep, actor, storage.DependencyAddOptions{}); err != nil {
					return fmt.Errorf("adding edge %s->%s: %w", fromID, toID, err)
				}
				if graphApplySchedulingDependencyType(depType) {
					newSchedulingEdges = append(newSchedulingEdges, [2]string{fromID, toID})
				}
			}

			// 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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped error for the storage-level cause (foreign key, duplicate, connection)
  2. Re-run bd graph apply after confirming both endpoint issues exist (bd show)
  3. Deduplicate the plan so the same edge isn't inserted twice
  4. Verify database health (bd doctor) and retry on a clean connection

Example fix

// before: applying a stale plan after the target issue was deleted
bd graph apply plan.json
// after: regenerate the plan and ensure targets exist
bd show task-2 && bd graph apply plan.json
Defensive patterns

Strategy: try-catch

Validate before calling

for _, e := range plan.Edges {
  if !issueExists(resolve(e.From)) || !issueExists(resolve(e.To)) {
    return fmt.Errorf("edge endpoint missing in db")
  }
}

Type guard

func depInsertable(e Edge, keyToID map[string]string, db DB) bool {
  return db.HasIssue(keyToID[e.FromKey]) && db.HasIssue(keyToID[e.ToKey])
}

Try / catch

if err := bd.GraphApply(ctx, plan); err != nil {
  if strings.Contains(err.Error(), "adding edge") {
    // transaction rolled back; safe to retry after fixing storage
    return retryGraphApply(ctx, plan)
  }
  return err
}

Prevention

When it happens

Trigger: tx.AddDependencyWithOptions(ctx, dep, actor, storage.DependencyAddOptions{}) returns an error after NewGraphEdgeDependency succeeded, e.g. fromID/toID exists in keyToID but the corresponding row is missing in storage, or a uniqueness/constraint violation.

Common situations: Concurrent modifications racing with the apply (issue deleted between planning and apply), database constraint conflicts from a partially-applied earlier attempt, or storage connectivity problems.

Related errors


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