gastownhall/beads · error

edge %d: must specify to_key or to_id

Error message

edge %d: must specify to_key or to_id

What it means

During `bd graph apply` plan validation, every edge must identify both endpoints. The 'to' endpoint may be given as a plan key (to_key) or an issue ID (to_id); when both are empty the edge is unresolvable, so validation rejects the plan with this error before any nodes or edges are written.

Source

Thrown at cmd/bd/graph_apply.go:603

				if !dt.IsValid() {
					return fmt.Errorf("node %q: dep %d: invalid dependency type %q", node.Key, j, dep.Type)
				}
			}
		}
	}

	for i, edge := range plan.Edges {
		if edge.FromKey != "" && !seenKeys[edge.FromKey] {
			return fmt.Errorf("edge %d: from key %q not found in plan", i, edge.FromKey)
		}
		if edge.ToKey != "" && !seenKeys[edge.ToKey] {
			return fmt.Errorf("edge %d: to key %q not found in plan", i, edge.ToKey)
		}
		if edge.FromKey == "" && edge.FromID == "" {
			return fmt.Errorf("edge %d: must specify from_key or from_id", i)
		}
		if edge.ToKey == "" && edge.ToID == "" {
			return fmt.Errorf("edge %d: must specify to_key or to_id", i)
		}
		if edge.Type != "" {
			dt := types.DependencyType(edge.Type)
			if !dt.IsValid() {
				return fmt.Errorf("edge %d: invalid dependency type %q", i, edge.Type)
			}
		}
		if edge.Gate != "" || edge.SpawnerKey != "" || edge.SpawnerID != "" {
			if graphApplyDependencyType(edge.Type) != types.DepWaitsFor {
				return fmt.Errorf("edge %d: gate/spawner fields require type %q", i, types.DepWaitsFor)
			}
			if edge.Gate != "" && !types.IsValidWaitsForGate(edge.Gate) {
				return fmt.Errorf("edge %d: invalid gate %q (valid: %s, %s)", i, edge.Gate, types.WaitsForAllChildren, types.WaitsForAnyChildren)
			}
			if edge.SpawnerKey != "" && edge.SpawnerID != "" {
				return fmt.Errorf("edge %d: cannot specify both spawner_key and spawner_id", i)
			}
			if edge.SpawnerKey != "" && !seenKeys[edge.SpawnerKey] {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Add a to_key referencing a node key defined in the same plan
  2. Or add a to_id referencing an existing issue ID
  3. Re-generate the plan with the tool that produced it instead of hand-editing

Example fix

// before
{"edges": [{"from_key": "auth-node"}]}
// after
{"edges": [{"from_key": "auth-node", "to_key": "db-node"}]}
Defensive patterns

Strategy: validation

Validate before calling

for i, e := range plan.Edges {
  if e.ToKey == "" && e.ToID == "" {
    return fmt.Errorf("edge %d: missing to endpoint (set to_key or to_id)", i)
  }
}

Type guard

func hasToEndpoint(e Edge) bool { return e.ToKey != "" || e.ToID != "" }

Prevention

When it happens

Trigger: A graph plan JSON/YAML contains an edge whose ToKey and ToID are both empty strings or omitted; the plan was hand-authored or generated by a tool that did not fill the to endpoint.

Common situations: Hand-editing a plan file and deleting the to_key; a generator emitting edges from templates with unfilled placeholders; copy-pasting an edge entry and removing the wrong field.

Related errors


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