gastownhall/beads · error

loop %q: body is required

Error message

loop %q: body is required

What it means

ApplyLoops validates every step's LoopSpec before expansion; a loop with an empty Body (no steps to repeat) is structurally invalid and aborts cooking with this error naming the offending step ID.

Source

Thrown at internal/formula/controlflow.go:59

		if err := validateLoopSpec(step.Loop, step.ID); err != nil {
			return nil, err
		}

		// Expand the loop
		expanded, err := expandLoop(step)
		if err != nil {
			return nil, err
		}
		result = append(result, expanded...)
	}

	return result, nil
}

// validateLoopSpec checks that a loop spec is valid.
func validateLoopSpec(loop *LoopSpec, stepID string) error {
	if len(loop.Body) == 0 {
		return fmt.Errorf("loop %q: body is required", stepID)
	}

	// Count the number of loop types specified
	loopTypes := 0
	if loop.Count > 0 {
		loopTypes++
	}
	if loop.Until != "" {
		loopTypes++
	}
	if loop.Range != "" {
		loopTypes++
	}

	if loopTypes == 0 {
		return fmt.Errorf("loop %q: one of count, until, or range is required", stepID)
	}
	if loopTypes > 1 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Add at least one step to the loop body before applying loops
  2. Skip setting Step.Loop entirely for steps that have no body (leave Loop nil)
  3. Validate loop specs at formula-load time with a clear step-ID report before invoking ApplyLoops

Example fix

// before
step.Loop = &formula.LoopSpec{Count: 3} // Body empty
steps, err := formula.ApplyLoops(steps) // loop "review": body is required
// after
step.Loop = &formula.LoopSpec{Count: 3, Body: []*formula.Step{bodyStep}}
steps, err := formula.ApplyLoops(steps)
Defensive patterns

Strategy: validation

Validate before calling

func checkLoop(loop *formula.LoopSpec, stepID string) error {
	if loop != nil && len(loop.Body) == 0 {
		return fmt.Errorf("step %s: loop declared but body empty", stepID)
	}
	return nil
}

Type guard

func hasLoopBody(s *formula.Step) bool {
	return s.Loop == nil || len(s.Loop.Body) > 0
}

Try / catch

steps, err := formula.ApplyLoops(steps)
if err != nil {
	if strings.Contains(err.Error(), "body is required") {
		return fmt.Errorf("formula has an empty loop: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling formula.ApplyLoops (during formula cooking) with a Step whose Loop is non-nil but Body has zero entries — e.g. a YAML/JSON loop step that declares loop metadata but no body steps.

Common situations: Hand-authored formula files with an empty loop block; programmatic generation that creates the LoopSpec before appending body steps; deleting all body steps in an editor and leaving the loop marker behind.

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/23582b5b779bee89. Report an issue: GitHub.