gastownhall/beads · error
serializing waits-for metadata: unresolved spawner key %q
Error message
serializing waits-for metadata: unresolved spawner key %q
What it means
NewGraphEdgeDependency builds a dependency row for a graph-plan edge. For waits-for edges it must record the spawner in metadata; if the plan supplies a plan-local spawnerKey that is absent from keyToID (the map of plan node keys to minted issue IDs), the spawner cannot be resolved and construction fails with this error.
Source
Thrown at internal/types/types.go:1389
// stored rows stay self-describing rather than depending on every reader
// defaulting a missing gate (the runtime SQL predicate COALESCEs to
// all-children, but readers before migration 0059 did not, so '{}' or empty
// metadata must never be stored for graph-created waits-for dependencies).
// A plan-local spawnerKey resolves through keyToID; the spawner is recorded
// for compatibility only — gate evaluation reads the spawner from
// dependencies.depends_on_id (see ParseWaitsForGateMetadata).
func NewGraphEdgeDependency(fromID, toID string, depType DependencyType, gate, spawnerKey, spawnerID, threadID string, keyToID map[string]string) (*Dependency, error) {
dep := &Dependency{
IssueID: fromID,
DependsOnID: toID,
Type: depType,
ThreadID: threadID,
}
if depType == DepWaitsFor {
if spawnerKey != "" {
resolved, ok := keyToID[spawnerKey]
if !ok {
return nil, fmt.Errorf("serializing waits-for metadata: unresolved spawner key %q", spawnerKey)
}
spawnerID = resolved
}
if gate == "" {
gate = WaitsForAllChildren
}
raw, err := json.Marshal(WaitsForMeta{Gate: gate, SpawnerID: spawnerID})
if err != nil {
return nil, fmt.Errorf("serializing waits-for metadata: %w", err)
}
dep.Metadata = string(raw)
}
return dep, nil
}
// NewWaitsForDependency builds the waits-for dependency record for a single
// issue outside a graph plan: the spawner is the depends_on target and the
// metadata carries the gate (defaulted to all-children). SharesView on GitHub (pinned to 71377f2769)
Solutions
- Ensure the spawner node exists in the graph plan and is included when building keyToID before edges are applied
- Fix the spawnerKey spelling in the plan edge so it matches the node key exactly
- Pass an empty spawnerKey (or the final spawnerID directly) when the spawner is already resolved — the code only consults keyToID when spawnerKey is non-empty
Example fix
// before NewGraphEdgeDependency(fromID, toID, DepWaitsFor, "", "node-7", "", "", keyToID) // "node-7" missing from plan // after // add the node keyed "node-7" to the plan first, or pass "" as spawnerKey NewGraphEdgeDependency(fromID, toID, DepWaitsFor, "", "", spawnerID, "", nil)
Defensive patterns
Strategy: validation
Validate before calling
func validateSpawnerKey(spawnerKey string, keyToID map[string]string) error {
if spawnerKey != "" && keyToID != nil {
if _, ok := keyToID[spawnerKey]; !ok {
return fmt.Errorf("spawner key %q not in plan: add the node before applying edges", spawnerKey)
}
}
return nil
} Type guard
func spawnerKeyResolved(spawnerKey string, keyToID map[string]string) bool {
_, ok := keyToID[spawnerKey]
return ok
} Prevention
- Build keyToID from the full node list before applying any edges
- Keep node keys in plans stable; avoid renaming keys mid-plan
- Prefer passing an already-resolved spawnerID when available
When it happens
Trigger: Calling NewGraphEdgeDependency with depType=DepWaitsFor and a non-empty spawnerKey that is not a key in the keyToID map passed by the graph-apply path (embedded or domain).
Common situations: A graph plan references a spawner node key that was never defined as a node, was renamed, or the keyToID map was built before that node was minted; hand-assembled plans or partial plan imports hitting this check.
Related errors
- metadata_ref %q references unknown key %q
- dep target %q not found
- no store is open for this workspace
- not found
- no absolute native user directory is available
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/72769187067fdf21.
Report an issue: GitHub.