{"record":{"id":"72769187067fdf21","repo":"gastownhall/beads","slug":"serializing-waits-for-metadata-unresolved-spawner","errorCode":null,"errorMessage":"serializing waits-for metadata: unresolved spawner key %q","messagePattern":"serializing waits-for metadata: unresolved spawner key %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/types/types.go","lineNumber":1389,"sourceCode":"// stored rows stay self-describing rather than depending on every reader\n// defaulting a missing gate (the runtime SQL predicate COALESCEs to\n// all-children, but readers before migration 0059 did not, so '{}' or empty\n// metadata must never be stored for graph-created waits-for dependencies).\n// A plan-local spawnerKey resolves through keyToID; the spawner is recorded\n// for compatibility only — gate evaluation reads the spawner from\n// dependencies.depends_on_id (see ParseWaitsForGateMetadata).\nfunc NewGraphEdgeDependency(fromID, toID string, depType DependencyType, gate, spawnerKey, spawnerID, threadID string, keyToID map[string]string) (*Dependency, error) {\n\tdep := &Dependency{\n\t\tIssueID:     fromID,\n\t\tDependsOnID: toID,\n\t\tType:        depType,\n\t\tThreadID:    threadID,\n\t}\n\tif depType == DepWaitsFor {\n\t\tif spawnerKey != \"\" {\n\t\t\tresolved, ok := keyToID[spawnerKey]\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"serializing waits-for metadata: unresolved spawner key %q\", spawnerKey)\n\t\t\t}\n\t\t\tspawnerID = resolved\n\t\t}\n\t\tif gate == \"\" {\n\t\t\tgate = WaitsForAllChildren\n\t\t}\n\t\traw, err := json.Marshal(WaitsForMeta{Gate: gate, SpawnerID: spawnerID})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"serializing waits-for metadata: %w\", err)\n\t\t}\n\t\tdep.Metadata = string(raw)\n\t}\n\treturn dep, nil\n}\n\n// NewWaitsForDependency builds the waits-for dependency record for a single\n// issue outside a graph plan: the spawner is the depends_on target and the\n// metadata carries the gate (defaulted to all-children). Shares","sourceCodeStart":1371,"sourceCodeEnd":1407,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/types/types.go#L1371-L1407","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nNewGraphEdgeDependency(fromID, toID, DepWaitsFor, \"\", \"node-7\", \"\", \"\", keyToID) // \"node-7\" missing from plan\n// after\n// add the node keyed \"node-7\" to the plan first, or pass \"\" as spawnerKey\nNewGraphEdgeDependency(fromID, toID, DepWaitsFor, \"\", \"\", spawnerID, \"\", nil)","handlingStrategy":"validation","validationCode":"func validateSpawnerKey(spawnerKey string, keyToID map[string]string) error {\n\tif spawnerKey != \"\" && keyToID != nil {\n\t\tif _, ok := keyToID[spawnerKey]; !ok {\n\t\t\treturn fmt.Errorf(\"spawner key %q not in plan: add the node before applying edges\", spawnerKey)\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":"func spawnerKeyResolved(spawnerKey string, keyToID map[string]string) bool {\n\t_, ok := keyToID[spawnerKey]\n\treturn ok\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["go","graph-plan","dependency-metadata","key-resolution"],"backgroundTag":"unresolved-plan-node-key","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}