gastownhall/beads · error

loop %q: max must be positive

Error message

loop %q: max must be positive

What it means

validateLoopSpec rejects a LoopSpec whose Max field is negative when a formula using loops is expanded (via ApplyLoops). Max caps the number of loop iterations, so a negative value is meaningless. Note the check is `loop.Max < 0`, so zero actually passes validation despite the 'must be positive' wording.

Source

Thrown at internal/formula/controlflow.go:90

	}

	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)
		}
	}

	// Validate range syntax if present
	if loop.Range != "" {
		if err := ValidateRange(loop.Range); err != nil {
			return fmt.Errorf("loop %q: invalid range %q: %w", stepID, loop.Range, err)
		}
	}

	return nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the formula definition so loop.max is zero or a positive integer
  2. If max should mean 'unlimited', omit the field or use 0, since only negative values are rejected
  3. Check whether max is computed from a variable and verify that variable's resolved value

Example fix

# before
loop:
  count: 3
  max: -1
# after
loop:
  count: 3
  max: 10
Defensive patterns

Strategy: validation

Validate before calling

func validateLoopMax(loop *LoopSpec) error {
	if loop != nil && loop.Max < 0 {
		return fmt.Errorf("loop.max must be >= 0, got %d", loop.Max)
	}
	return nil
}

Type guard

func hasValidMax(loop *LoopSpec) bool { return loop == nil || loop.Max >= 0 }

Try / catch

steps, err := formula.ApplyLoops(steps)
if err != nil {
	var msg string
	if strings.Contains(err.Error(), "max must be positive") {
		msg = "formula loop.max is negative; fix the loop spec"
	}
	return fmt.Errorf("loop config error: %v %s", err, msg)
}

Prevention

When it happens

Trigger: Calling ApplyLoops (directly or via formula Apply/expand) on a Step whose Loop.Max is set to a negative number, typically from a YAML/formula file where max was typoed or computed from a negative expression.

Common situations: Hand-written formula YAML with `max: -1` intended as 'unlimited'; a max value computed from a variable that resolved negative; copy-paste of count/max config with a stray minus sign.

Related errors


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