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
- Add at least one step to the loop body before applying loops
- Skip setting Step.Loop entirely for steps that have no body (leave Loop nil)
- 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
- Never set Step.Loop for steps without body steps
- Validate loop bodies at formula-parse time, before cooking
- Lint formula files for empty loop blocks
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
- applying control flow: %w
- loop %q: one of count, until, or range is required
- loop %q: only one of count, until, or range can be specified
- loop %q: max is required when until is set
- loop %q: count must be positive
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/23582b5b779bee89.
Report an issue: GitHub.