gastownhall/beads · error
edge %d: gate/spawner fields require type %q
Error message
edge %d: gate/spawner fields require type %q
What it means
Gate and spawner fields (gate, spawner_key, spawner_id) only have meaning for waits-for dependencies. If any of these fields is set on an edge whose effective dependency type is not waits-for, graph apply rejects the plan, since the fields would be silently ignored otherwise.
Source
Thrown at cmd/bd/graph_apply.go:613
}
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] {
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 {View on GitHub (pinned to 71377f2769)
Solutions
- Set edge.type to "waits-for" on edges that carry gate/spawner fields
- Remove the gate/spawner fields if the edge should be a plain dependency
- If converting an edge to waits-for, also satisfy the spawner/gate constraints (see related errors)
Example fix
// before
{"from_key": "a", "to_key": "b", "type": "related", "gate": "all-children"}
// after
{"from_key": "a", "to_key": "b", "type": "waits-for", "gate": "all-children"} Defensive patterns
Strategy: validation
Validate before calling
if (e.Gate != "" || e.SpawnerKey != "" || e.SpawnerID != "") && e.Type != "waits-for" {
return fmt.Errorf("edge with gate/spawner fields must have type waits-for")
} Type guard
func gateFieldsSet(e Edge) bool { return e.Gate != "" || e.SpawnerKey != "" || e.SpawnerID != "" } Prevention
- Only attach gate/spawner fields to waits-for edges
- Have the generator assert type == waits-for whenever it emits gate fields
- Document waits-for requirements in plan templates
When it happens
Trigger: An edge sets gate, spawner_key, or spawner_id while edge.type is empty (defaulting to a non-waits-for type) or set to another type like blocks/related.
Common situations: Template-generated plans adding gate metadata to plain dependency edges; author hand-adding a gate to an existing non-waits-for edge; a converter mapping fields without adjusting type.
Related errors
- ExternalDoltConfig: Port requires Host
- ExternalDoltConfig: TLSCert set without TLSKey
- --waits-for-gate requires --waits-for (no spawner ID specifi
- invalid --waits-for-gate value %q (valid: all-children, any-
- invalid graph plan: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/39c280b89222e254.
Report an issue: GitHub.