gastownhall/beads · error

applying branches: %w

Error message

applying branches: %w

What it means

ApplyControlFlow wraps failures from applyBranchesWithMap with 'applying branches: %w'. Branch rules wire fork-join dependency patterns: all branch steps depend on the 'from' step and the 'join' step depends on all branch steps. This error means a branch rule is missing a required field (from, steps, join) or references a step ID that does not exist in the step map.

Source

Thrown at internal/formula/controlflow.go:567

// 3. Gates (add condition labels)
//
// Returns a new steps slice. The original steps slice is not modified.
func ApplyControlFlow(steps []*Step, compose *ComposeRules) ([]*Step, error) {
	var err error

	// Apply loops first (expands steps) - ApplyLoops already returns new slice
	steps, err = ApplyLoops(steps)
	if err != nil {
		return nil, fmt.Errorf("applying loops: %w", err)
	}

	// Build stepMap once for branches and gates
	// No need to clone here since ApplyLoops already returned a new slice
	stepMap := buildStepMap(steps)

	// Apply branches (wires dependencies)
	if err := applyBranchesWithMap(stepMap, compose); err != nil {
		return nil, fmt.Errorf("applying branches: %w", err)
	}

	// Apply gates (adds labels)
	if err := applyGatesWithMap(stepMap, compose); err != nil {
		return nil, fmt.Errorf("applying gates: %w", err)
	}

	return steps, nil
}

// cloneStepDeep creates a deep copy of a step including children.
func cloneStepDeep(s *Step) *Step {
	clone := cloneStep(s)

	if len(s.Children) > 0 {
		clone.Children = make([]*Step, len(s.Children))
		for i, child := range s.Children {
			clone.Children[i] = cloneStepDeep(child)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped inner error — it says exactly which field is missing or which step ID was not found
  2. Verify every branch From, Join, and Steps ID exists in the formula after loop expansion (expansion can rename/replace steps)
  3. Run ApplyLoops first and inspect the expanded step IDs to confirm branch targets survive expansion
  4. Correct the branch rule IDs or remove the stale rule in the compose section

Example fix

// before
branches:
  - from: plan
    steps: [build-api, build-web]
    join: intergrate
// after
branches:
  - from: plan
    steps: [build-api, build-web]
    join: integrate
Defensive patterns

Strategy: validation

Validate before calling

for _, b := range compose.Branch {
	if b.From == "" || b.Join == "" || len(b.Steps) == 0 {
		return fmt.Errorf("branch rule incomplete: from=%q join=%q steps=%d", b.From, b.Join, len(b.Steps))
	}
	ids := buildStepMap(steps) // includes nested children
	for _, id := range append([]string{b.From, b.Join}, b.Steps...) {
		if _, ok := ids[id]; !ok {
			return fmt.Errorf("branch references missing step %q", id)
		}
	}
}

Try / catch

out, err := ApplyControlFlow(steps, compose)
if err != nil {
	if strings.Contains(err.Error(), "applying branches") {
		return fmt.Errorf("invalid branch rule in compose: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling ApplyControlFlow(steps, compose) where a compose.Branch entry has empty From/Join/Steps, or where From, Join, or any Steps entry names a step ID absent from the formula (checked via stepMap built after loop expansion).

Common situations: Typos in branch from/join/step IDs in formula compose config; a branch rule pointing at a step created or renamed by a loop expansion under different IDs; deleting a step without updating the branch rule.

Related errors


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