{"record":{"id":"0457f8805705a4a5","repo":"gastownhall/beads","slug":"branch-from-is-required","errorCode":null,"errorMessage":"branch: from is required","messagePattern":"branch: from is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/controlflow.go","lineNumber":446,"sourceCode":"\tif err := applyBranchesWithMap(stepMap, compose); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cloned, nil\n}\n\n// applyBranchesWithMap applies branch rules using a pre-built stepMap.\n// This is the internal implementation used by both ApplyBranches and ApplyControlFlow.\n// The stepMap entries are modified in place.\nfunc applyBranchesWithMap(stepMap map[string]*Step, compose *ComposeRules) error {\n\tif compose == nil || len(compose.Branch) == 0 {\n\t\treturn nil\n\t}\n\n\tfor _, branch := range compose.Branch {\n\t\t// Validate the branch rule\n\t\tif branch.From == \"\" {\n\t\t\treturn fmt.Errorf(\"branch: from is required\")\n\t\t}\n\t\tif len(branch.Steps) == 0 {\n\t\t\treturn fmt.Errorf(\"branch: steps is required\")\n\t\t}\n\t\tif branch.Join == \"\" {\n\t\t\treturn fmt.Errorf(\"branch: join is required\")\n\t\t}\n\n\t\t// Verify all steps exist\n\t\tif _, ok := stepMap[branch.From]; !ok {\n\t\t\treturn fmt.Errorf(\"branch: from step %q not found\", branch.From)\n\t\t}\n\t\tif _, ok := stepMap[branch.Join]; !ok {\n\t\t\treturn fmt.Errorf(\"branch: join step %q not found\", branch.Join)\n\t\t}\n\t\tfor _, stepID := range branch.Steps {\n\t\t\tif _, ok := stepMap[stepID]; !ok {\n\t\t\t\treturn fmt.Errorf(\"branch: parallel step %q not found\", stepID)","sourceCodeStart":428,"sourceCodeEnd":464,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/controlflow.go#L428-L464","documentation":"applyBranchesWithMap validates each ComposeRules branch rule and requires a From step ID identifying where the branch fan-out begins. An empty From makes the branch rule unusable, so ApplyBranches/ApplyControlFlow fails before modifying the step graph.","triggerScenarios":"Calling ApplyBranches(steps, compose) or ApplyControlFlow with a compose.Branch entry whose From field is empty/omitted in the formula's compose section.","commonSituations":"YAML branch block missing the `from:` key; indentation error placing the rule under the wrong parent; building ComposeRules programmatically and forgetting to set From.","solutions":["Add the missing `from:` key naming the fan-out step in the formula's branch rule","Check YAML indentation so the branch entry actually contains its from field","Ensure the field name matches the schema (from, not source/start)"],"exampleFix":"# before\nbranch:\n  - steps: [a, b]\n    join: c\n# after\nbranch:\n  - from: start\n    steps: [a, b]\n    join: c","handlingStrategy":"validation","validationCode":"for i, b := range compose.Branch {\n\tif b.From == \"\" {\n\t\treturn fmt.Errorf(\"branch[%d]: from is required\", i)\n\t}\n}","typeGuard":null,"tryCatchPattern":"steps, err := formula.ApplyBranches(steps, compose)\nif err != nil {\n\tif strings.Contains(err.Error(), \"from is required\") {\n\t\treturn fmt.Errorf(\"compose branch missing 'from': %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Always include from/steps/join in every branch rule","Lint formula YAML against the compose schema before loading","Watch for YAML indentation that silently drops nested keys"],"tags":["go","formula","branch","validation","compose"],"backgroundTag":"missing-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}