gastownhall/beads · error

branch: steps is required

Error message

branch: steps is required

What it means

Each branch rule must list the Steps that run in parallel between From and Join. applyBranchesWithMap rejects a rule with an empty Steps list because a branch with no parallel steps does nothing and likely indicates a misconfigured compose section.

Source

Thrown at internal/formula/controlflow.go:449

	return cloned, nil
}

// 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)
			}
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. List the parallel step IDs under the branch rule's `steps:` key
  2. Verify the listed steps actually exist elsewhere in the formula (a later check requires it)
  3. Uncomment or restore step entries that were accidentally removed

Example fix

# before
branch:
  - from: start
    join: end
# after
branch:
  - from: start
    steps: [a, b]
    join: end
Defensive patterns

Strategy: validation

Validate before calling

for i, b := range compose.Branch {
	if len(b.Steps) == 0 {
		return fmt.Errorf("branch[%d]: steps is required", i)
	}
}

Try / catch

steps, err := formula.ApplyBranches(steps, compose)
if err != nil {
	if strings.Contains(err.Error(), "steps is required") {
		return fmt.Errorf("compose branch missing 'steps': %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: ApplyBranches or ApplyControlFlow encountering a compose.Branch entry with no `steps` entries (empty list or omitted key).

Common situations: YAML `steps:` key present but with no list items; all step entries commented out; programmatically built ComposeRules with a nil Steps slice.

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/d22c5222977ed331. Report an issue: GitHub.