gastownhall/beads · error
branch: parallel step %q not found
Error message
branch: parallel step %q not found
What it means
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.
Source
Thrown at internal/formula/controlflow.go:464
return fmt.Errorf("branch: from is required")
}
if len(branch.Steps) == 0 {
return fmt.Errorf("branch: steps is required")
}
if branch.Join == "" {
return fmt.Errorf("branch: join is required")
}
// Verify all steps exist
if _, ok := stepMap[branch.From]; !ok {
return fmt.Errorf("branch: from step %q not found", branch.From)
}
if _, ok := stepMap[branch.Join]; !ok {
return fmt.Errorf("branch: join step %q not found", branch.Join)
}
for _, stepID := range branch.Steps {
if _, ok := stepMap[stepID]; !ok {
return fmt.Errorf("branch: parallel step %q not found", stepID)
}
}
// Add dependencies: branch steps depend on 'from'
for _, stepID := range branch.Steps {
step := stepMap[stepID]
step.Needs = appendUnique(step.Needs, branch.From)
}
// Add dependencies: 'join' depends on all branch steps
joinStep := stepMap[branch.Join]
for _, stepID := range branch.Steps {
joinStep.Needs = appendUnique(joinStep.Needs, stepID)
}
}
return nil
}View on GitHub (pinned to 71377f2769)
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
Example fix
// before (compose rules)
branch: [{from: build, steps: [test-unit, test-int], join: report}]
// steps define "test-integration", not "test-int"
// after
branch: [{from: build, steps: [test-unit, test-integration], join: report}] Defensive patterns
Strategy: validation
Validate before calling
func validateBranchSteps(steps []*Step, compose *ComposeRules) error {
ids := make(map[string]bool, len(steps))
for _, s := range steps {
ids[s.ID] = true
}
for _, b := range compose.Branch {
for _, id := range b.Steps {
if !ids[id] {
return fmt.Errorf("branch references unknown parallel step %q", id)
}
}
}
return nil
} Try / catch
if _, err := formula.ApplyControlFlow(steps, compose); err != nil {
var missing string
if n, _ := fmt.Sscanf(err.Error(), "branch: parallel step %q not found", &missing); n == 1 {
return fmt.Errorf("workflow config error: branch lists step %q; defined steps: %v", missing, stepIDs(steps))
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- gate: target step %q not found
- gate: before is required
- gate: condition is required
- formula %q not accessible: %w
- loop %q: max must be positive
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/77acf3f14b1fbf6e.
Report an issue: GitHub.