gastownhall/beads · error

applyGraph: edge %d references undefined to_key %q

Error message

applyGraph: edge %d references undefined to_key %q

What it means

Same fail-fast validation as the from_key check, but applied to the edge's to-side: applyGraph resolves Edges[i].ToKey/ToID against keyToID and aborts the entire graph apply if the dependency target does not exist, preserving failure-before-write semantics.

Source

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

	parentDepPairs := graphParentDepPairs(plan.Nodes, keyToID)
	newSchedulingEdges := make([][2]string, 0, len(plan.Nodes)+len(plan.Edges))
	if err := u.validatePlannedBlockingPaths(ctx, plan, keyToID, parentDepPairs); err != nil {
		return GraphApplyResult{}, err
	}
	if err := u.validatePlannedBlockingCycles(ctx, plan, keyToID); err != nil {
		return GraphApplyResult{}, err
	}
	// Preserve failure-before-write for explicit edges that conflict directly
	// with an implicit node parent relationship. Parent-first mutation below is
	// for transitive hierarchy visibility, not for deferring structural errors.
	for i, edge := range plan.Edges {
		fromID := resolveEdgeRef(edge.FromKey, edge.FromID, keyToID)
		if fromID == "" {
			return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d references undefined from_key %q", i, edge.FromKey)
		}
		toID := resolveEdgeRef(edge.ToKey, edge.ToID, keyToID)
		if toID == "" {
			return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d references undefined to_key %q", i, edge.ToKey)
		}
		depType := edge.Type
		if depType == "" {
			depType = types.DepBlocks
		}
		if parentDepPairs[depPairKey(fromID, toID)] && depType != types.DepParentChild {
			return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d %s->%s duplicates a parent-child relationship with dependency type %q", i, fromID, toID, depType)
		}
		if parentDepPairs[depPairKey(toID, fromID)] && cycleRelevantDepType(depType) {
			return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d %s->%s creates a blocking reverse of a parent-child relationship", i, fromID, toID)
		}
	}

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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Create the target issue (in the plan or beforehand) so ToKey resolves
  2. Check the target key with bd show; fix typos
  3. Use edge.ToID when the UUID is known
  4. Sync first if the target lives on another machine/remote

Example fix

// before
GraphEdge{FromKey: "bd-1", ToKey: "bd-blocked-by-missing"}
// after
GraphEdge{FromKey: "bd-1", ToKey: "bd-2"} // ensure bd-2 exists or is a node in the same plan
Defensive patterns

Strategy: validation

Validate before calling

for _, e := range plan.Edges {
    if _, ok := keyToID[e.ToKey]; !ok && e.ToID == "" {
        return fmt.Errorf("edge to_key %q not found; create it or add a node", e.ToKey)
    }
}

Type guard

func edgeToResolved(e GraphEdge, keyToID map[string]string) bool {
    _, ok := keyToID[e.ToKey]
    return ok || e.ToID != ""
}

Prevention

When it happens

Trigger: applyGraph plan contains an edge whose ToKey (with no valid ToID) names an issue absent from the DB and from the plan's own nodes.

Common situations: Referencing a blocker that was closed-and-deleted; typo in the dependency target key; plan split across two applies where the target was expected in the other half; key from a different project prefix.

Related errors


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