gastownhall/beads · error
branch: from step %q not found
Error message
branch: from step %q not found
What it means
applyBranchesWithMap verifies that the rule's From step exists in the step map built from the formula's steps. If the ID is set but does not match any step, expansion fails — this catches dangling references after renames or typos.
Source
Thrown at internal/formula/controlflow.go:457
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]
step.Needs = appendUnique(step.Needs, branch.From)
}
// Add dependencies: 'join' depends on all branch steps
joinStep := stepMap[branch.Join]View on GitHub (pinned to 71377f2769)
Solutions
- Update the branch rule's from to the current ID of the fan-out step
- List the formula's step IDs and diff them against from/steps/join references
- Restore the deleted step or remove the stale branch rule
- Check for casing/whitespace differences in the ID
Example fix
# before - from: strat steps: [a, b] join: end # after - from: start steps: [a, b] join: end
Defensive patterns
Strategy: validation
Validate before calling
func checkBranchFrom(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.From] {
return fmt.Errorf("branch from %q not found", b.From)
}
}
return nil
} Try / catch
steps, err := formula.ApplyBranches(steps, compose)
if err != nil {
if strings.Contains(err.Error(), "from step") && strings.Contains(err.Error(), "not found") {
return fmt.Errorf("stale branch 'from' reference: %w", err)
}
return err
} Prevention
- Cross-check branch from/steps/join IDs against step IDs in CI
- Search for all references before renaming a step ID
- Keep step IDs exact-match clean: no casing or whitespace drift
When it happens
Trigger: ApplyBranches/ApplyControlFlow with a branch rule whose From names a step ID absent from the steps list (typo, renamed/deleted step, wrong casing).
Common situations: Renaming a step without updating the branch rule's from reference; copy-pasting branch config from another formula; case mismatch since step IDs are matched exactly.
Related errors
- branch: join 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/089da282ad01c500.
Report an issue: GitHub.