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

  1. Fix the range or the variable values so end >= start
  2. Clamp or swap the values before invoking the formula (e.g. pass max(start,end) as end)
  3. Add pre-expansion validation of resolved variable values in the caller
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/5b757d087b6af26c. Report an issue: GitHub.