{"record":{"id":"77acf3f14b1fbf6e","repo":"gastownhall/beads","slug":"branch-parallel-step-q-not-found","errorCode":null,"errorMessage":"branch: parallel step %q not found","messagePattern":"branch: parallel step %q not found","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/controlflow.go","lineNumber":464,"sourceCode":"\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)\n\t\t\t}\n\t\t}\n\n\t\t// Add dependencies: branch steps depend on 'from'\n\t\tfor _, stepID := range branch.Steps {\n\t\t\tstep := stepMap[stepID]\n\t\t\tstep.Needs = appendUnique(step.Needs, branch.From)\n\t\t}\n\n\t\t// Add dependencies: 'join' depends on all branch steps\n\t\tjoinStep := stepMap[branch.Join]\n\t\tfor _, stepID := range branch.Steps {\n\t\t\tjoinStep.Needs = appendUnique(joinStep.Needs, stepID)\n\t\t}\n\t}\n\n\treturn nil\n}","sourceCodeStart":446,"sourceCodeEnd":482,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/controlflow.go#L446-L482","documentation":"applyBranchesWithMap validates every step ID listed in a branch rule's Steps slice against the workflow's step map before wiring fork-join dependencies. This error is returned when a parallel-step ID referenced in a `branch` compose rule does not match any step defined in the steps list. It exists to fail fast with the offending ID instead of silently skipping dependency wiring.","triggerScenarios":"Calling ApplyBranches or ApplyControlFlow with a ComposeRules whose Branch[i].Steps contains a stepID that is absent from the steps slice — typically a typo in the step ID, a step defined with a different ID, or a step removed/renamed while the branch rule still lists the old ID.","commonSituations":"Hand-edited workflow YAML/JSON where a branch rule references a step that was renamed; copy-pasting a branch block from another workflow; ID case mismatch (IDs are matched exactly); a loop expansion removing or renaming the target step.","solutions":["Check the workflow's step definitions and correct the parallel step ID in the branch rule's steps list so it matches a defined step ID exactly","Verify the step wasn't renamed or removed by a recent edit and update the branch rule accordingly","Check for case or whitespace mismatches between the rule and the step ID (matching is exact string lookup)","If the step should exist, confirm ApplyControlFlow's loop expansion (which runs before branches) isn't renaming it"],"exampleFix":"// before (compose rules)\nbranch: [{from: build, steps: [test-unit, test-int], join: report}]\n// steps define \"test-integration\", not \"test-int\"\n// after\nbranch: [{from: build, steps: [test-unit, test-integration], join: report}]","handlingStrategy":"validation","validationCode":"func validateBranchSteps(steps []*Step, compose *ComposeRules) error {\n\tids := make(map[string]bool, len(steps))\n\tfor _, s := range steps {\n\t\tids[s.ID] = true\n\t}\n\tfor _, b := range compose.Branch {\n\t\tfor _, id := range b.Steps {\n\t\t\tif !ids[id] {\n\t\t\t\treturn fmt.Errorf(\"branch references unknown parallel step %q\", id)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"if _, err := formula.ApplyControlFlow(steps, compose); err != nil {\n\tvar missing string\n\tif n, _ := fmt.Sscanf(err.Error(), \"branch: parallel step %q not found\", &missing); n == 1 {\n\t\treturn fmt.Errorf(\"workflow config error: branch lists step %q; defined steps: %v\", missing, stepIDs(steps))\n\t}\n\treturn err\n}","preventionTips":["Generate branch step lists from the same source of truth that defines the steps, not by hand","Add a config lint step that cross-checks every branch/steps ID against defined step IDs before running","Keep step IDs in named constants or an enum-like set to avoid typos","Re-check branch rules whenever a step is renamed or removed"],"tags":["go","formula","workflow-validation","step-not-found"],"backgroundTag":"referenced-step-not-found","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}