{"record":{"id":"505141f4b531e54c","repo":"gastownhall/beads","slug":"loop-q-w","errorCode":null,"errorMessage":"loop %q: %w","messagePattern":"loop %q: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/controlflow.go","lineNumber":146,"sourceCode":"\t\t}\n\n\t\t// Recursively expand any nested loops FIRST\n\t\tvar err error\n\t\tresult, err = ApplyLoops(result)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// THEN chain iterations on the expanded result\n\t\t// This must happen AFTER recursive expansion so we chain the final steps\n\t\tif step.Loop.Count > 1 {\n\t\t\tresult = chainExpandedIterations(result, step.ID, step.Loop.Count)\n\t\t}\n\t} else if step.Loop.Range != \"\" {\n\t\t// Range loop: expand body for each value in the computed range\n\t\trangeSpec, err := ParseRange(step.Loop.Range, vars)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"loop %q: %w\", step.ID, err)\n\t\t}\n\n\t\t// Validate range\n\t\tif rangeSpec.End < rangeSpec.Start {\n\t\t\treturn nil, fmt.Errorf(\"loop %q: range end (%d) is less than start (%d)\",\n\t\t\t\tstep.ID, rangeSpec.End, rangeSpec.Start)\n\t\t}\n\n\t\t// Expand body for each value in range\n\t\tcount := rangeSpec.End - rangeSpec.Start + 1\n\t\titerNum := 0\n\t\tfor val := rangeSpec.Start; val <= rangeSpec.End; val++ {\n\t\t\titerNum++\n\t\t\t// Build iteration vars: include the loop variable if specified\n\t\t\titerVars := make(map[string]string)\n\t\t\tif step.Loop.Var != \"\" {\n\t\t\t\titerVars[step.Loop.Var] = fmt.Sprintf(\"%d\", val)\n\t\t\t}","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/controlflow.go#L128-L164","documentation":"expandLoopWithVars parses the step's Loop.Range with ParseRange using the provided template variables and wraps any parse failure. This is the variable-substitution range path: the range string is expanded with vars before parsing, and if the resulting spec is unparseable the loop expansion aborts.","triggerScenarios":"expandLoop (called during formula expansion with variables) seeing a Loop.Range that, after variable substitution, still fails ParseRange — e.g. unresolved `{{var}}` placeholders or malformed bounds.","commonSituations":"A variable referenced in the range was not provided in the vars map, so `{{n}}` remains literal; a variable resolves to a non-numeric string; typo in the range separator after substitution.","solutions":["Ensure every variable used in the range expression is present in the vars map and resolves to an integer","Print/log the substituted range string to see what ParseRange actually received","Fix the range syntax in the formula definition","Use validateLoopSpec/ApplyLoops preflight to catch malformed ranges before expansion"],"exampleFix":"// before\nexpandLoop(step, map[string]string{\"start\": \"one\"}) // range: \"{{start}}..3\"\n// after\nexpandLoop(step, map[string]string{\"start\": \"1\"})","handlingStrategy":"validation","validationCode":"func checkRangeVars(r string, vars map[string]string) error {\n\tfor _, m := range regexp.MustCompile(`\\{\\{(\\w+)\\}\\}`).FindAllStringSubmatch(r, -1) {\n\t\tif _, ok := vars[m[1]]; !ok {\n\t\t\treturn fmt.Errorf(\"range %q: missing variable %q\", r, m[1])\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"expanded, err := expandLoops(steps, vars)\nif err != nil {\n\tif strings.Contains(err.Error(), \"loop \\\"\") {\n\t\treturn fmt.Errorf(\"range loop failed to parse; check vars resolve to integers: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Ensure every variable referenced in a range is supplied in the vars map","Verify variable values are numeric strings before expansion","Log the substituted range string when debugging expansion failures"],"tags":["go","formula","range","template-variables","loops"],"backgroundTag":"invalid-range-expression","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}