gastownhall/beads · error
loop %q: only one of count, until, or range can be specified
Error message
loop %q: only one of count, until, or range can be specified
What it means
LoopSpecs are mutually exclusive in their iteration mode: specifying two or more of Count, Until, or Range is ambiguous, so validateLoopSpec fails with this error before any expansion happens.
Source
Thrown at internal/formula/controlflow.go:78
}
// 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 present
if loop.Until != "" {
if _, err := ParseCondition(loop.Until); err != nil {
return fmt.Errorf("loop %q: invalid until condition %q: %w", stepID, loop.Until, err)View on GitHub (pinned to 71377f2769)
Solutions
- Keep exactly one of Count, Until, or Range per LoopSpec; delete the others
- If merging loop configs, resolve to one driver explicitly instead of combining fields
- For a bounded conditional loop, express the bound via Max (with Until), not by adding Count
Example fix
// before
loop := &formula.LoopSpec{Count: 3, Until: "step.status == 'complete'", Max: 10, Body: body}
steps, err := formula.ApplyLoops(steps) // only one of count, until, or range can be specified
// after
loop := &formula.LoopSpec{Until: "step.status == 'complete'", Max: 10, Body: body}
steps, err := formula.ApplyLoops(steps) Defensive patterns
Strategy: validation
Validate before calling
if loopDriverSet(loop) > 1 {
return fmt.Errorf("loop specifies multiple drivers (count/until/range); pick one")
}
steps, err := formula.ApplyLoops(steps) Type guard
func singleLoopDriver(l *formula.LoopSpec) bool {
return (b2i(l.Count > 0) + b2i(l.Until != "") + b2i(l.Range != "")) <= 1
} Try / catch
steps, err := formula.ApplyLoops(steps)
if err != nil {
if strings.Contains(err.Error(), "only one of count, until, or range") {
return fmt.Errorf("conflicting loop config: %w", err)
}
return err
} Prevention
- When merging loop configs, resolve to a single driver explicitly
- Never auto-populate a Count default when the user supplied Until or Range
- Use Max (not Count) to bound until-loops
When it happens
Trigger: ApplyLoops with a LoopSpec where e.g. Count>0 and Until!="", or Until!="" and Range!="" — often from merging configs or programmatically setting defaults alongside user-specified values.
Common situations: Merging a template loop (with default Count) with a user loop (with Until); auto-fill code that sets Count=1 as a default while the user also supplied a Range; JSON/YAML that includes both count and until keys.
Related errors
- applying control flow: %w
- loop %q: body is required
- loop %q: one of count, until, or range is required
- 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/85a0d0947ee98a87.
Report an issue: GitHub.