gastownhall/beads · error

loop %q: invalid until condition %q: %w

Error message

loop %q: invalid until condition %q: %w

What it means

validateLoopSpec parses the loop's Until condition with ParseCondition and wraps any parse failure. The until condition is a condition expression evaluated each iteration to stop the loop; if its syntax is invalid the loop can never be safely expanded.

Source

Thrown at internal/formula/controlflow.go:96

		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
}

// expandLoop expands a loop step into its constituent steps.
func expandLoop(step *Step) ([]*Step, error) {
	return expandLoopWithVars(step, nil)
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped inner error from ParseCondition for the exact syntax problem
  2. Rewrite the until condition using the library's documented condition syntax
  3. Test the condition by calling ParseCondition directly before shipping the formula
  4. Simplify the condition (e.g. single comparison) to isolate the failing part

Example fix

# before
loop:
  until: "count > "
# after
loop:
  until: "count > 5"
Defensive patterns

Strategy: validation

Validate before calling

func validateUntil(cond string) error {
	if cond == "" {
		return nil
	}
	_, err := formula.ParseCondition(cond)
	return err
}

Try / catch

steps, err := formula.ApplyLoops(steps)
if err != nil {
	if strings.Contains(err.Error(), "invalid until condition") {
		return fmt.Errorf("fix loop.until syntax: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: ApplyLoops encountering a LoopSpec with a non-empty Until string that ParseCondition cannot parse (malformed comparison, unknown operator, unbalanced quoting, empty operand).

Common situations: Typo in a condition like `items.count >` (missing operand); using shell-style syntax instead of the library's condition grammar; quoting issues in YAML that mangle the expression; referencing unsupported functions.

Related errors


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