gastownhall/beads · error
branch: join is required
Error message
branch: join is required
What it means
Each branch rule must name a Join step where the parallel paths converge. applyBranchesWithMap rejects rules with an empty Join because the library cannot reconstruct execution ordering without a convergence point.
Source
Thrown at internal/formula/controlflow.go:452
// applyBranchesWithMap applies branch rules using a pre-built stepMap.
// This is the internal implementation used by both ApplyBranches and ApplyControlFlow.
// The stepMap entries are modified in place.
func applyBranchesWithMap(stepMap map[string]*Step, compose *ComposeRules) error {
if compose == nil || len(compose.Branch) == 0 {
return nil
}
for _, branch := range compose.Branch {
// Validate the branch rule
if branch.From == "" {
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]View on GitHub (pinned to 71377f2769)
Solutions
- Add `join:` naming the convergence step in each branch rule
- Confirm the join step is a real step ID in the formula (checked immediately after)
- Re-check YAML indentation so join belongs to the same branch list item
Example fix
# before
branch:
- from: start
steps: [a, b]
# after
branch:
- from: start
steps: [a, b]
join: end Defensive patterns
Strategy: validation
Validate before calling
for i, b := range compose.Branch {
if b.Join == "" {
return fmt.Errorf("branch[%d]: join is required", i)
}
} Try / catch
steps, err := formula.ApplyBranches(steps, compose)
if err != nil {
if strings.Contains(err.Error(), "join is required") {
return fmt.Errorf("compose branch missing 'join': %w", err)
}
return err
} Prevention
- Always specify the convergence step explicitly; there is no default join
- Validate compose rules when formulas are loaded, not at expansion time
- Check indentation so join belongs to the branch list item
When it happens
Trigger: ApplyBranches or ApplyControlFlow encountering a compose.Branch entry whose Join field is empty/omitted.
Common situations: YAML branch block missing `join:`; assuming join defaults to the last step (it does not); programmatic construction of ComposeRules leaving Join unset.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- branch: from is required
- branch: steps is required
- branch: from step %q not found
- branch: join step %q not found
- applying expansions: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f53f0cb07d224dcd.
Report an issue: GitHub.