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
- Add a to_key referencing a node key defined in the same plan
- Or add a to_id referencing an existing issue ID
- 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
- Always emit both from and to endpoints in generated plans
- Validate plans with a schema before running bd graph apply
- Keep spawner/gate edges on the to endpoint when generating
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
- got %d close reasons for %d issue IDs; provide exactly one s
- cannot specify both --reason-file and --reason/--resolution/
- --reason-file %q is empty; close reason is required
- invalid mode '%s', must be 'compile' or 'runtime'
- runtime mode requires all variables to have values Missing:
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6fc0b7129267f1d9.
Report an issue: GitHub.