gastownhall/beads · error
node %q: %w
Error message
node %q: %w
What it means
validateGraphApplyNodeFields wraps errors from validating a node's explicit ID with the node's plan key: when node.ID is set, it must pass ValidateIDFormat, and any format violation is reported as `node "<key>": <underlying reason>`. The wrap keeps the plan key visible even though the failing value is the ID.
Source
Thrown at cmd/bd/graph_apply.go:653
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 for
// bd-create parity, mirroring the flag-shape checks `bd create` applies
// (config-gated template linting is not run on graph plans).
func validateGraphApplyNodeFields(node GraphApplyNode, customTypes, customStatuses []string, opts GraphApplyOptions) error {
if node.ID != "" {
if _, err := validation.ValidateIDFormat(node.ID); err != nil {
return fmt.Errorf("node %q: %w", node.Key, err)
}
}
// Friendlier status message than the issue-model validator's below.
if node.Status != "" && !types.Status(node.Status).IsValidWithCustom(customStatuses) {
return fmt.Errorf("node %q: invalid status %q (valid: %s; configure custom statuses via 'bd config set status.custom')", node.Key, node.Status, workapi.ValidStatusList(customStatuses))
}
if node.WispType != "" && !types.WispType(node.WispType).IsValid() {
return fmt.Errorf("node %q: invalid wisp_type %q (must be %s)", node.Key, node.WispType, types.ValidWispTypeNames())
}
if node.MolType != "" && !types.MolType(node.MolType).IsValid() {
return fmt.Errorf("node %q: invalid mol_type %q (must be %s)", node.Key, node.MolType, types.ValidMolTypeNames())
}
if (node.EventKind != "" || node.Actor != "" || node.Target != "" || node.Payload != "") && node.Type != string(types.TypeEvent) {
return fmt.Errorf("node %q: event_kind, actor, target, and payload require type %q", node.Key, types.TypeEvent)
}
// Issue-model rules (type validity, priority range, estimate sign,
// metadata JSON, ...) run against the same materialized issue the apply
// path stores, so plan-time and insert-time validation can't drift.View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped underlying message and fix node.id to satisfy the ID format rules
- Remove node.id and let bd assign/resolve the ID when possible
- Sanitize generated IDs (lowercase, allowed charset, separators) in your plan generator
Example fix
// before
{"key": "auth", "id": "Auth Feature #1"}
// after
{"key": "auth", "id": "auth-feature-1"} Defensive patterns
Strategy: validation
Validate before calling
for _, n := range plan.Nodes {
if n.ID != "" {
if err := validation.ValidateIDFormat(n.ID); err != nil { return fmt.Errorf("node %q: %w", n.Key, err) }
}
} Type guard
func validNodeID(n Node) bool { return n.ID == "" || validation.ValidateIDFormat(n.ID) == nil } Try / catch
if err := bd.GraphApply(ctx, plan); err != nil {
var fmtErr *fmt.Errorf
if strings.Contains(err.Error(), "invalid") { /* fix plan and retry */ }
return err
} Prevention
- Sanitize generated IDs (lowercase alphanumerics and hyphens only)
- Let bd assign IDs instead of hand-writing them when possible
- Lint node id fields with the same validator bd uses before apply
When it happens
Trigger: A graph plan node sets an id field that violates ID format rules (bad characters, wrong length, invalid prefix/shape), e.g. id: "my issue!" or an ID containing uppercase/spaces.
Common situations: Hand-written plans using human-readable labels as IDs; generating IDs from names without sanitization; copy-pasting IDs from external systems with different formats.
Related errors
- invalid graph plan: %w
- edge %d: must specify to_key or to_id
- edge %d: invalid dependency type %q
- edge %d: gate/spawner fields require type %q
- invalid ID format '%s' (expected format: prefix-hash or pref
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f1ce6e34b323e79d.
Report an issue: GitHub.