{"record":{"id":"0dc92f19a54fb31b","repo":"gastownhall/beads","slug":"loop-q-invalid-until-condition-q-w","errorCode":null,"errorMessage":"loop %q: invalid until condition %q: %w","messagePattern":"loop %q: invalid until condition %q: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/controlflow.go","lineNumber":96,"sourceCode":"\t\treturn fmt.Errorf(\"loop %q: only one of count, until, or range can be specified\", stepID)\n\t}\n\n\tif loop.Until != \"\" && loop.Max == 0 {\n\t\treturn fmt.Errorf(\"loop %q: max is required when until is set\", stepID)\n\t}\n\n\tif loop.Count < 0 {\n\t\treturn fmt.Errorf(\"loop %q: count must be positive\", stepID)\n\t}\n\n\tif loop.Max < 0 {\n\t\treturn fmt.Errorf(\"loop %q: max must be positive\", stepID)\n\t}\n\n\t// Validate until condition syntax if present\n\tif loop.Until != \"\" {\n\t\tif _, err := ParseCondition(loop.Until); err != nil {\n\t\t\treturn fmt.Errorf(\"loop %q: invalid until condition %q: %w\", stepID, loop.Until, err)\n\t\t}\n\t}\n\n\t// Validate range syntax if present\n\tif loop.Range != \"\" {\n\t\tif err := ValidateRange(loop.Range); err != nil {\n\t\t\treturn fmt.Errorf(\"loop %q: invalid range %q: %w\", stepID, loop.Range, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// expandLoop expands a loop step into its constituent steps.\nfunc expandLoop(step *Step) ([]*Step, error) {\n\treturn expandLoopWithVars(step, nil)\n}\n","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/controlflow.go#L78-L114","documentation":"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.","triggerScenarios":"ApplyLoops encountering a LoopSpec with a non-empty Until string that ParseCondition cannot parse (malformed comparison, unknown operator, unbalanced quoting, empty operand).","commonSituations":"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.","solutions":["Read the wrapped inner error from ParseCondition for the exact syntax problem","Rewrite the until condition using the library's documented condition syntax","Test the condition by calling ParseCondition directly before shipping the formula","Simplify the condition (e.g. single comparison) to isolate the failing part"],"exampleFix":"# before\nloop:\n  until: \"count > \"\n# after\nloop:\n  until: \"count > 5\"","handlingStrategy":"validation","validationCode":"func validateUntil(cond string) error {\n\tif cond == \"\" {\n\t\treturn nil\n\t}\n\t_, err := formula.ParseCondition(cond)\n\treturn err\n}","typeGuard":null,"tryCatchPattern":"steps, err := formula.ApplyLoops(steps)\nif err != nil {\n\tif strings.Contains(err.Error(), \"invalid until condition\") {\n\t\treturn fmt.Errorf(\"fix loop.until syntax: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Call ParseCondition on every until condition as a preflight before loading formulas","Keep until conditions to simple documented comparisons","Beware YAML quoting that mangles the condition string"],"tags":["go","formula","validation","condition","loops"],"backgroundTag":"invalid-condition-syntax","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}