gastownhall/beads · error
loop %q: range end (%d) is less than start (%d)
Error message
loop %q: range end (%d) is less than start (%d)
What it means
After parsing, expandLoopWithVars checks that the range's End is not less than its Start; a descending range would produce a negative iteration count, so expansion fails with the computed bounds in the message. Note this is the vars-expansion path only — validateLoopSpec does not catch this because bounds may come from substituted variables.
Source
Thrown at internal/formula/controlflow.go:151
if err != nil {
return nil, err
}
// THEN chain iterations on the expanded result
// This must happen AFTER recursive expansion so we chain the final steps
if step.Loop.Count > 1 {
result = chainExpandedIterations(result, step.ID, step.Loop.Count)
}
} else if step.Loop.Range != "" {
// Range loop: expand body for each value in the computed range
rangeSpec, err := ParseRange(step.Loop.Range, vars)
if err != nil {
return nil, fmt.Errorf("loop %q: %w", step.ID, err)
}
// Validate range
if rangeSpec.End < rangeSpec.Start {
return nil, fmt.Errorf("loop %q: range end (%d) is less than start (%d)",
step.ID, rangeSpec.End, rangeSpec.Start)
}
// Expand body for each value in range
count := rangeSpec.End - rangeSpec.Start + 1
iterNum := 0
for val := rangeSpec.Start; val <= rangeSpec.End; val++ {
iterNum++
// Build iteration vars: include the loop variable if specified
iterVars := make(map[string]string)
if step.Loop.Var != "" {
iterVars[step.Loop.Var] = fmt.Sprintf("%d", val)
}
iterSteps, err := expandLoopIteration(step, iterNum, iterVars)
if err != nil {
return nil, err
}
result = append(result, iterSteps...)View on GitHub (pinned to 71377f2769)
Solutions
- Fix the range or the variable values so end >= start
- Clamp or swap the values before invoking the formula (e.g. pass max(start,end) as end)
- Add pre-expansion validation of resolved variable values in the caller
- If descending iteration is intended, it is unsupported — generate an explicit list or separate steps
Example fix
// before
vars := map[string]string{"start": "10", "end": "3"} // range: "{{start}}..{{end}}"
// after
vars := map[string]string{"start": "3", "end": "10"} Defensive patterns
Strategy: validation
Validate before calling
start, _ := strconv.Atoi(vars["start"])
end, _ := strconv.Atoi(vars["end"])
if end < start {
return fmt.Errorf("range end (%d) must be >= start (%d)", end, start)
} Try / catch
expanded, err := expandLoops(steps, vars)
if err != nil {
if strings.Contains(err.Error(), "is less than start") {
return fmt.Errorf("swap or clamp range bounds before expansion: %w", err)
}
return err
} Prevention
- Clamp or swap start/end values before invoking formula expansion
- Validate user-supplied range bounds at the input boundary
- Remember descending ranges are unsupported
When it happens
Trigger: A Loop.Range like `10..3`, or variables that resolve so start > end (e.g. `{{start}}..{{end}}` with start=10, end=3), reaching expandLoopWithVars during formula expansion.
Common situations: Variables computed in the wrong order (end derived from a smaller value); user input supplying bounds where end < start; hard-coded reversed range typo.
Related errors
- loop %q: invalid range %q: %w
- loop %q: max must be positive
- loop %q: invalid until condition %q: %w
- loop %q: %w
- applying control flow: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/5b757d087b6af26c.
Report an issue: GitHub.