gastownhall/beads · error
loop %q: one of count, until, or range is required
Error message
loop %q: one of count, until, or range is required
What it means
A LoopSpec must specify exactly one repetition driver: Count, Until, or Range. If none is set, validateLoopSpec rejects it with this error because the expansion logic would have no iteration semantics.
Source
Thrown at internal/formula/controlflow.go:75
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 {
return fmt.Errorf("loop %q: only one of count, until, or range can be specified", stepID)
}
if loop.Until != "" && loop.Max == 0 {
return fmt.Errorf("loop %q: max is required when until is set", stepID)
}
if loop.Count < 0 {
return fmt.Errorf("loop %q: count must be positive", stepID)
}
if loop.Max < 0 {
return fmt.Errorf("loop %q: max must be positive", stepID)
}
// Validate until condition syntax if presentView on GitHub (pinned to 71377f2769)
Solutions
- Set exactly one of Loop.Count (fixed repetitions), Loop.Until (condition string, requires Max), or Loop.Range
- For a capped conditional loop, set both Until and Max: `&LoopSpec{Until: "step.status == 'complete'", Max: 10, Body: body}`
- Pre-validate LoopSpec before ApplyLoops: body non-empty plus exactly one of Count>0 / Until / Range
Example fix
// before
loop := &formula.LoopSpec{Max: 10, Body: body} // no driver
steps, err := formula.ApplyLoops(steps) // loop "build": one of count, until, or range is required
// after
loop := &formula.LoopSpec{Until: "step.status == 'complete'", Max: 10, Body: body}
steps, err := formula.ApplyLoops(steps) Defensive patterns
Strategy: validation
Validate before calling
func loopDriverSet(l *formula.LoopSpec) int {
n := 0
if l.Count > 0 { n++ }
if l.Until != "" { n++ }
if l.Range != "" { n++ }
return n
}
if loop != nil && len(loop.Body) > 0 && loopDriverSet(loop) == 0 {
return fmt.Errorf("loop needs count, until, or range")
} Type guard
func hasLoopDriver(l *formula.LoopSpec) bool {
return l.Count > 0 || l.Until != "" || l.Range != ""
} Try / catch
steps, err := formula.ApplyLoops(steps)
if err != nil {
if strings.Contains(err.Error(), "one of count, until, or range is required") {
return fmt.Errorf("loop misconfigured: %w", err)
}
return err
} Prevention
- Set exactly one iteration driver on every LoopSpec
- Remember Max alone is not a driver; pair it with Until
- Pre-validate specs with the same rule ApplyLoops enforces
When it happens
Trigger: ApplyLoops encountering a step whose Loop has a non-empty Body but Count<=0, Until=="", and Range=="" — e.g. `&LoopSpec{Body: body}` or `&LoopSpec{Max: 5, Body: body}` (Max alone is not a driver).
Common situations: Authors assuming Max alone makes a conditional loop (Max only caps Until loops); a Until condition removed by variable substitution leaving an empty string; copy-pasting a loop template and deleting the count/until line.
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: body 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/6b21cec466e707c1.
Report an issue: GitHub.