gastownhall/beads · error
edge %d: spawner_key %q cannot be combined with to_id %q (to
Error message
edge %d: spawner_key %q cannot be combined with to_id %q (to_id overrides to_key as the waits-for target; use spawner_id)
What it means
Gate evaluation reads the spawner from the dependency target (depends_on_id), and to_id overrides to_key when resolving the edge's to endpoint. Combining a key-based spawner with a to_id would make the spawner and target diverge, so the combination is rejected; use spawner_id instead.
Source
Thrown at cmd/bd/graph_apply.go:629
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] {
return fmt.Errorf("edge %d: spawner key %q not found in plan", i, edge.SpawnerKey)
}
// Gate evaluation reads the spawner from the dependency target
// (depends_on_id), not metadata, so the spawner must equal the to
// endpoint. Since to_id overrides to_key at apply time
// (resolveEdgeRef), a key-named spawner can't be combined with to_id.
if edge.SpawnerKey != "" && edge.ToID != "" {
return fmt.Errorf("edge %d: spawner_key %q cannot be combined with to_id %q (to_id overrides to_key as the waits-for target; use spawner_id)", i, edge.SpawnerKey, edge.ToID)
}
if edge.SpawnerKey != "" && edge.SpawnerKey != edge.ToKey {
return fmt.Errorf("edge %d: spawner_key %q must match to_key %q (the waits-for target is the spawner)", i, edge.SpawnerKey, edge.ToKey)
}
if edge.SpawnerID != "" && edge.SpawnerID != edge.ToID {
return fmt.Errorf("edge %d: spawner_id %q must match to_id %q (the waits-for target is the spawner)", i, edge.SpawnerID, edge.ToID)
}
}
}
if err := validateGraphApplyLocalCycles(plan, seenKeys); err != nil {
return err
}
return nil
}
// validateGraphApplyNodeFields checks the single-node fields added forView on GitHub (pinned to 71377f2769)
Solutions
- Replace spawner_key with spawner_id matching to_id
- Or drop to_id and address the to endpoint by to_key that equals spawner_key
Example fix
// before
{"type": "waits-for", "spawner_key": "parent", "to_id": "bd-42"}
// after
{"type": "waits-for", "spawner_id": "bd-42", "to_id": "bd-42"} Defensive patterns
Strategy: validation
Validate before calling
if e.SpawnerKey != "" && e.ToID != "" {
return fmt.Errorf("edge: use spawner_id when the to endpoint is addressed by to_id")
} Type guard
func spawnerCompat(e Edge) bool { return !(e.SpawnerKey != "" && e.ToID != "") } Prevention
- Keep one addressing style (keys or IDs) per edge
- Remember to_id wins over to_key at apply time
- When enriching key plans with IDs, convert spawner_key to spawner_id too
When it happens
Trigger: An edge sets both spawner_key and to_id. Even if spawner_key matches to_key, apply resolves the target via to_id, so the key-based spawner would be wrong.
Common situations: Plans where a tool added to_id for external references while other edges retain key-based spawners; mixing ID-addressed and key-addressed edges in one waits-for family.
Related errors
- edge %d: gate/spawner fields require type %q
- edge %d: invalid gate %q (valid: %s, %s)
- edge %d: cannot specify both spawner_key and spawner_id
- edge %d: spawner key %q not found in plan
- edge %d: spawner_key %q must match to_key %q (the waits-for
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4239ce6ab0acef1a.
Report an issue: GitHub.