{"record":{"id":"0562dcda510f8272","repo":"micro/go-micro","slug":"flow-loop-requires-a-body-step","errorCode":null,"errorMessage":"flow: Loop requires a body step","messagePattern":"flow: Loop requires a body step","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"flow/loop.go","lineNumber":84,"sourceCode":"//\t    ),\n//\t)\n//\n// The loop runs as a single flow step: the flow checkpoints the loop's\n// outcome, and a resume re-enters the step, so loop bodies should be safe to\n// repeat. Use OnIteration to record per-pass progress. If the cap is hit\n// before the stop condition fires, the loop returns the latest state rather\n// than erroring — the guardrail did its job.\nfunc Loop(body StepFunc, opts ...LoopOption) StepFunc {\n\to := LoopOptions{Max: 10}\n\tfor _, op := range opts {\n\t\top(&o)\n\t}\n\tif o.Max <= 0 {\n\t\to.Max = 10\n\t}\n\treturn func(ctx context.Context, in State) (State, error) {\n\t\tif body == nil {\n\t\t\treturn in, fmt.Errorf(\"flow: Loop requires a body step\")\n\t\t}\n\t\tcur := in\n\t\tfor iter := 1; iter <= o.Max; iter++ {\n\t\t\tout, err := body(ctx, cur)\n\t\t\tif err != nil {\n\t\t\t\treturn cur, fmt.Errorf(\"loop iteration %d: %w\", iter, err)\n\t\t\t}\n\t\t\tcur = out\n\t\t\tif o.OnIter != nil {\n\t\t\t\to.OnIter(iter, cur)\n\t\t\t}\n\t\t\tdone, err := loopDone(ctx, o, cur, iter)\n\t\t\tif err != nil {\n\t\t\t\treturn cur, err\n\t\t\t}\n\t\t\tif done {\n\t\t\t\treturn cur, nil\n\t\t\t}","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/flow/loop.go#L66-L102","documentation":"The Loop step combinator was created with a nil body step, so when the returned step function runs it immediately returns this error instead of executing zero iterations. The library guards against a nil body because looping nothing is always a programming mistake.","triggerScenarios":"Using flow.Loop with a body argument that is nil — e.g. a conditionally-built step variable left nil, or passing a function-typed field that was never assigned.","commonSituations":"Building steps dynamically where an if-branch forgot to assign the body; refactoring that renamed a variable leaving a nil step; unmarshalling pipeline definitions where the body step is missing.","solutions":["Pass a non-nil body step to Loop; check the variable before calling.","Provide a fallback/no-op step when the body may legitimately be absent.","If steps come from config, validate that the body step is defined before constructing the pipeline."],"exampleFix":"// before\nvar body flow.Step // left nil\nout, err := flow.Loop(flow.Max(5))(ctx, in) // body nil\n// after\nbody := flow.StepFunc(func(ctx context.Context, in flow.State) (flow.State, error) { return in, nil })\nout, err := flow.Loop(flow.Max(5))(ctx, in)","handlingStrategy":"validation","validationCode":"func safeLoop(opts ...flow.LoopOption, body flow.Step) flow.Step {\n\tif body == nil { body = flow.StepFunc(func(ctx context.Context, in flow.State) (flow.State, error) { return in, nil }) }\n\treturn flow.Loop(opts...)(nil, body)\n}","typeGuard":"func isNilStep(s flow.Step) bool { return s == nil || reflect.ValueOf(s).IsNil() }","tryCatchPattern":"out, err := loopStep(ctx, in)\nif err != nil && strings.Contains(err.Error(), \"requires a body step\") {\n\treturn fmt.Errorf(\"pipeline misconfigured: loop body was nil: %w\", err)\n}","preventionTips":["Never pass function-typed step variables without checking for nil first.","Build pipelines through constructors that reject nil steps eagerly.","Unit-test pipeline construction with every config permutation."],"tags":["flow","loop","nil-step","programming-error"],"backgroundTag":"missing-required-step","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}