{"record":{"id":"23582b5b779bee89","repo":"gastownhall/beads","slug":"loop-q-body-is-required","errorCode":null,"errorMessage":"loop %q: body is required","messagePattern":"loop %q: body is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/controlflow.go","lineNumber":59,"sourceCode":"\t\tif err := validateLoopSpec(step.Loop, step.ID); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Expand the loop\n\t\texpanded, err := expandLoop(step)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult = append(result, expanded...)\n\t}\n\n\treturn result, nil\n}\n\n// validateLoopSpec checks that a loop spec is valid.\nfunc validateLoopSpec(loop *LoopSpec, stepID string) error {\n\tif len(loop.Body) == 0 {\n\t\treturn fmt.Errorf(\"loop %q: body is required\", stepID)\n\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 {","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/controlflow.go#L41-L77","documentation":"ApplyLoops validates every step's LoopSpec before expansion; a loop with an empty Body (no steps to repeat) is structurally invalid and aborts cooking with this error naming the offending step ID.","triggerScenarios":"Calling formula.ApplyLoops (during formula cooking) with a Step whose Loop is non-nil but Body has zero entries — e.g. a YAML/JSON loop step that declares loop metadata but no body steps.","commonSituations":"Hand-authored formula files with an empty loop block; programmatic generation that creates the LoopSpec before appending body steps; deleting all body steps in an editor and leaving the loop marker behind.","solutions":["Add at least one step to the loop body before applying loops","Skip setting Step.Loop entirely for steps that have no body (leave Loop nil)","Validate loop specs at formula-load time with a clear step-ID report before invoking ApplyLoops"],"exampleFix":"// before\nstep.Loop = &formula.LoopSpec{Count: 3} // Body empty\nsteps, err := formula.ApplyLoops(steps) // loop \"review\": body is required\n// after\nstep.Loop = &formula.LoopSpec{Count: 3, Body: []*formula.Step{bodyStep}}\nsteps, err := formula.ApplyLoops(steps)","handlingStrategy":"validation","validationCode":"func checkLoop(loop *formula.LoopSpec, stepID string) error {\n\tif loop != nil && len(loop.Body) == 0 {\n\t\treturn fmt.Errorf(\"step %s: loop declared but body empty\", stepID)\n\t}\n\treturn nil\n}","typeGuard":"func hasLoopBody(s *formula.Step) bool {\n\treturn s.Loop == nil || len(s.Loop.Body) > 0\n}","tryCatchPattern":"steps, err := formula.ApplyLoops(steps)\nif err != nil {\n\tif strings.Contains(err.Error(), \"body is required\") {\n\t\treturn fmt.Errorf(\"formula has an empty loop: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Never set Step.Loop for steps without body steps","Validate loop bodies at formula-parse time, before cooking","Lint formula files for empty loop blocks"],"tags":["formula","loops","validation"],"backgroundTag":"missing-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}