gastownhall/beads · error
branch: join step %q not found
Error message
branch: join step %q not found
What it means
applyBranchesWithMap verifies the rule's Join step exists in the step map, just like From. A Join pointing at a nonexistent step ID means the convergence point is dangling and the branch cannot be applied.
Source
Thrown at internal/formula/controlflow.go:460
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]
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)
}View on GitHub (pinned to 71377f2769)
Solutions
- Update join to the current ID of the convergence step
- Verify the join step is a top-level step (not nested inside a loop body)
- Remove the branch rule if the join step no longer exists
- Check casing/whitespace in the join ID
Example fix
# before - from: start steps: [a, b] join: finsh # after - from: start steps: [a, b] join: finish
Defensive patterns
Strategy: validation
Validate before calling
func checkBranchJoin(steps []*Step, compose *formula.ComposeRules) error {
ids := map[string]bool{}
for _, s := range steps { ids[s.ID] = true }
for _, b := range compose.Branch {
if !ids[b.Join] {
return fmt.Errorf("branch join %q not found", b.Join)
}
}
return nil
} Try / catch
steps, err := formula.ApplyBranches(steps, compose)
if err != nil {
if strings.Contains(err.Error(), "join step") && strings.Contains(err.Error(), "not found") {
return fmt.Errorf("stale branch 'join' reference: %w", err)
}
return err
} Prevention
- Keep join steps at top level, not inside loop bodies
- Cross-check join IDs against step IDs whenever steps change
- Add a formula linter that validates all branch references exist
When it happens
Trigger: ApplyBranches/ApplyControlFlow with a branch rule whose Join names a step ID not present among the formula's steps.
Common situations: Join step deleted or renamed during refactoring; typo in the join ID; referring to a step defined inside a loop body that is not a top-level step at compose time.
Related errors
- branch: from step %q not found
- branch: from is required
- branch: steps is required
- branch: join is required
- applying inline expansions: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/25d9ab1b5a111ce3.
Report an issue: GitHub.