{"record":{"id":"85a0d0947ee98a87","repo":"gastownhall/beads","slug":"loop-q-only-one-of-count-until-or-range-can-be","errorCode":null,"errorMessage":"loop %q: only one of count, until, or range can be specified","messagePattern":"loop %q: only one of count, until, or range can be specified","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/controlflow.go","lineNumber":78,"sourceCode":"\t}\n\n\t// Count the number of loop types specified\n\tloopTypes := 0\n\tif loop.Count > 0 {\n\t\tloopTypes++\n\t}\n\tif loop.Until != \"\" {\n\t\tloopTypes++\n\t}\n\tif loop.Range != \"\" {\n\t\tloopTypes++\n\t}\n\n\tif loopTypes == 0 {\n\t\treturn fmt.Errorf(\"loop %q: one of count, until, or range is required\", stepID)\n\t}\n\tif loopTypes > 1 {\n\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)","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/controlflow.go#L60-L96","documentation":"LoopSpecs are mutually exclusive in their iteration mode: specifying two or more of Count, Until, or Range is ambiguous, so validateLoopSpec fails with this error before any expansion happens.","triggerScenarios":"ApplyLoops with a LoopSpec where e.g. Count>0 and Until!=\"\", or Until!=\"\" and Range!=\"\" — often from merging configs or programmatically setting defaults alongside user-specified values.","commonSituations":"Merging a template loop (with default Count) with a user loop (with Until); auto-fill code that sets Count=1 as a default while the user also supplied a Range; JSON/YAML that includes both count and until keys.","solutions":["Keep exactly one of Count, Until, or Range per LoopSpec; delete the others","If merging loop configs, resolve to one driver explicitly instead of combining fields","For a bounded conditional loop, express the bound via Max (with Until), not by adding Count"],"exampleFix":"// before\nloop := &formula.LoopSpec{Count: 3, Until: \"step.status == 'complete'\", Max: 10, Body: body}\nsteps, err := formula.ApplyLoops(steps) // only one of count, until, or range can be specified\n// after\nloop := &formula.LoopSpec{Until: \"step.status == 'complete'\", Max: 10, Body: body}\nsteps, err := formula.ApplyLoops(steps)","handlingStrategy":"validation","validationCode":"if loopDriverSet(loop) > 1 {\n\treturn fmt.Errorf(\"loop specifies multiple drivers (count/until/range); pick one\")\n}\nsteps, err := formula.ApplyLoops(steps)","typeGuard":"func singleLoopDriver(l *formula.LoopSpec) bool {\n\treturn (b2i(l.Count > 0) + b2i(l.Until != \"\") + b2i(l.Range != \"\")) <= 1\n}","tryCatchPattern":"steps, err := formula.ApplyLoops(steps)\nif err != nil {\n\tif strings.Contains(err.Error(), \"only one of count, until, or range\") {\n\t\treturn fmt.Errorf(\"conflicting loop config: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["When merging loop configs, resolve to a single driver explicitly","Never auto-populate a Count default when the user supplied Until or Range","Use Max (not Count) to bound until-loops"],"tags":["formula","loops","validation"],"backgroundTag":"conflicting-options","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}