gastownhall/beads · error

applying loops: %w

Error message

applying loops: %w

What it means

ApplyControlFlow wraps any failure from ApplyLoops with 'applying loops: %w'. ApplyLoops expands loop rules (a step repeated over an items list or count) into multiple concrete steps. This error means one of the compose loop rules failed validation or expansion — typically a missing field or a loop target step ID that does not exist in the formula's step list.

Source

Thrown at internal/formula/controlflow.go:558

		step.Labels = appendUnique(step.Labels, gateLabel)
	}

	return nil
}

// ApplyControlFlow applies all control flow operators in the correct order:
// 1. Loops (expand iterations)
// 2. Branches (wire fork-join dependencies)
// 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
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped inner error (errors.Unwrap or %v of the returned error) — it names the offending loop rule and field
  2. Check that every compose.Loops[].Target matches an existing step ID in the formula (including nested children)
  3. Run/inspect the loop rule validation before calling ApplyControlFlow by calling ApplyLoops(steps) directly in a test
  4. Fix or remove the invalid loop rule in the formula's compose section

Example fix

// before
compose:
  loops:
    - target: deploy-stp
      items: [dev, prod]
// after
compose:
  loops:
    - target: deploy-step
      items: [dev, prod]
Defensive patterns

Strategy: validation

Validate before calling

for _, lp := range compose.Loops {
	if lp.Target == "" {
		return fmt.Errorf("loop rule missing target")
	}
	found := false
	var walk func([]*Step)
	walk = func(ss []*Step) {
		for _, s := range ss {
			if s.ID == lp.Target { found = true }
			walk(s.Children)
		}
	}
	walk(steps)
	if !found {
		return fmt.Errorf("loop target %q not found in steps", lp.Target)
	}
}
_, err := ApplyLoops(steps)
return err

Try / catch

expanded, err := ApplyControlFlow(steps, compose)
if err != nil {
	var inner error = errors.Unwrap(err)
	log.Printf("control flow failed: %v (cause: %v)", err, inner)
	return fmt.Errorf("check compose loop rules: %w", err)
}

Prevention

When it happens

Trigger: Calling ApplyControlFlow(steps, compose) where compose.Loops contains a rule whose Target does not match any step ID, or whose items/count fields are invalid, causing ApplyLoops to return an error.

Common situations: Formula YAML where a loop rule references a step ID with a typo or renamed ID; a loop rule added after the target step was deleted; refactorings that renamed steps without updating the compose loop section.

Related errors


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