gastownhall/beads · error

applyGraph: edge %d references undefined from_key %q

Error message

applyGraph: edge %d references undefined from_key %q

What it means

applyGraph() validates every edge in an incoming graph plan before writing anything: it resolves each edge's FromKey (or FromID) against the set of issue IDs known to this transaction (keyToID). If the from-side key does not resolve to an existing or same-plan issue, the whole apply is aborted with this error so no partial graph is persisted.

Source

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

	// Build the (childID, parentID) pair set and validate that any planned
	// parent-child link does not close a cycle through planned edges or
	// already-existing dependencies in the store. This must run before any
	// dep inserts to catch the violation before we've written anything.
	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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Ensure the from-issue is created in the same plan (add a node) or already exists via bd list/show before applying
  2. Verify the issue key spelling (bd show <key> must succeed first)
  3. Set edge.FromID explicitly if you have the UUID and the key is unreliable
  4. Sync/pull so remote issues referenced by the plan exist locally

Example fix

// before
plan.Edges = append(plan.Edges, GraphEdge{FromKey: "bd-999", ToKey: "bd-1"}) // bd-999 doesn't exist
// after
if _, err := s.GetIssue(ctx, "bd-999"); err != nil {
    plan.Nodes = append(plan.Nodes, GraphNode{Key: "bd-999"}) // include it in the plan
}
plan.Edges = append(plan.Edges, GraphEdge{FromKey: "bd-999", ToKey: "bd-1"})
Defensive patterns

Strategy: validation

Validate before calling

for _, e := range plan.Edges {
    if _, ok := keyToID[e.FromKey]; !ok && e.FromID == "" {
        return fmt.Errorf("edge from_key %q has no node and no FromID", e.FromKey)
    }
}

Type guard

func edgeFromResolved(e GraphEdge, keyToID map[string]string) bool {
    _, byKey := keyToID[e.FromKey]
    return byKey || e.FromID != ""
}

Prevention

When it happens

Trigger: Calling applyGraph with a plan whose Edges[i].FromKey names an issue that is not in the database and not created by an earlier node in the same plan, and where no FromID fallback is set.

Common situations: Client built the plan against a different database/worktree; issue key typo'd or stale after deletion; edge references an issue from another repo that was never synced; plan JSON was hand-edited.

Related errors


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