{"record":{"id":"d4e8b3ada5208757","repo":"gastownhall/beads","slug":"edge-d-cannot-specify-both-spawner-key-and-spawn","errorCode":null,"errorMessage":"edge %d: cannot specify both spawner_key and spawner_id","messagePattern":"edge (.+?): cannot specify both spawner_key and spawner_id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/graph_apply.go","lineNumber":619,"sourceCode":"\t\t}\n\t\tif edge.ToKey == \"\" && edge.ToID == \"\" {\n\t\t\treturn fmt.Errorf(\"edge %d: must specify to_key or to_id\", i)\n\t\t}\n\t\tif edge.Type != \"\" {\n\t\t\tdt := types.DependencyType(edge.Type)\n\t\t\tif !dt.IsValid() {\n\t\t\t\treturn fmt.Errorf(\"edge %d: invalid dependency type %q\", i, edge.Type)\n\t\t\t}\n\t\t}\n\t\tif edge.Gate != \"\" || edge.SpawnerKey != \"\" || edge.SpawnerID != \"\" {\n\t\t\tif graphApplyDependencyType(edge.Type) != types.DepWaitsFor {\n\t\t\t\treturn fmt.Errorf(\"edge %d: gate/spawner fields require type %q\", i, types.DepWaitsFor)\n\t\t\t}\n\t\t\tif edge.Gate != \"\" && !types.IsValidWaitsForGate(edge.Gate) {\n\t\t\t\treturn fmt.Errorf(\"edge %d: invalid gate %q (valid: %s, %s)\", i, edge.Gate, types.WaitsForAllChildren, types.WaitsForAnyChildren)\n\t\t\t}\n\t\t\tif edge.SpawnerKey != \"\" && edge.SpawnerID != \"\" {\n\t\t\t\treturn fmt.Errorf(\"edge %d: cannot specify both spawner_key and spawner_id\", i)\n\t\t\t}\n\t\t\tif edge.SpawnerKey != \"\" && !seenKeys[edge.SpawnerKey] {\n\t\t\t\treturn fmt.Errorf(\"edge %d: spawner key %q not found in plan\", i, edge.SpawnerKey)\n\t\t\t}\n\t\t\t// Gate evaluation reads the spawner from the dependency target\n\t\t\t// (depends_on_id), not metadata, so the spawner must equal the to\n\t\t\t// endpoint. Since to_id overrides to_key at apply time\n\t\t\t// (resolveEdgeRef), a key-named spawner can't be combined with to_id.\n\t\t\tif edge.SpawnerKey != \"\" && edge.ToID != \"\" {\n\t\t\t\treturn 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)\n\t\t\t}\n\t\t\tif edge.SpawnerKey != \"\" && edge.SpawnerKey != edge.ToKey {\n\t\t\t\treturn fmt.Errorf(\"edge %d: spawner_key %q must match to_key %q (the waits-for target is the spawner)\", i, edge.SpawnerKey, edge.ToKey)\n\t\t\t}\n\t\t\tif edge.SpawnerID != \"\" && edge.SpawnerID != edge.ToID {\n\t\t\t\treturn fmt.Errorf(\"edge %d: spawner_id %q must match to_id %q (the waits-for target is the spawner)\", i, edge.SpawnerID, edge.ToID)\n\t\t\t}\n\t\t}","sourceCodeStart":601,"sourceCodeEnd":637,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/graph_apply.go#L601-L637","documentation":"A waits-for edge identifies its spawner either by plan key (spawner_key) or by issue ID (spawner_id), never both. Supplying both is ambiguous, so validation fails the plan rather than picking one arbitrarily.","triggerScenarios":"An edge sets both spawner_key and spawner_id, typically after a tool enriched a key-only plan with IDs, or hand-merging two edge definitions.","commonSituations":"Merging plans from two generators; post-processing scripts that fill in spawner_id without clearing spawner_key; manual edits adding the ID for clarity.","solutions":["Delete spawner_id and keep spawner_key when the spawner node lives in this plan","Delete spawner_key and keep spawner_id when referencing an issue outside the plan (and use to_id accordingly)","Regenerate the plan so exactly one spawner identifier is emitted"],"exampleFix":"// before\n{\"type\": \"waits-for\", \"spawner_key\": \"parent\", \"spawner_id\": \"bd-42\"}\n// after\n{\"type\": \"waits-for\", \"spawner_key\": \"parent\", \"to_key\": \"parent\"}","handlingStrategy":"validation","validationCode":"if e.SpawnerKey != \"\" && e.SpawnerID != \"\" {\n  return fmt.Errorf(\"edge: specify only one of spawner_key or spawner_id\")\n}","typeGuard":"func hasSingleSpawnerRef(e Edge) bool { return (e.SpawnerKey != \"\") != (e.SpawnerID != \"\") }","tryCatchPattern":null,"preventionTips":["Choose key-based or ID-based addressing per plan, not both","Post-processors that fill IDs should clear the corresponding key field","Add a plan-lint rule for mutually exclusive fields"],"tags":["graph-plan","waits-for","ambiguity"],"backgroundTag":"ambiguous-spawner-reference","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}