{"record":{"id":"be0bbbe2dc069f45","repo":"nektos/act","slug":"missing-steps-in-composite-action-be0bbb","errorCode":null,"errorMessage":"missing steps in composite action","messagePattern":"missing steps in composite action","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/runner/action_composite.go","lineNumber":94,"sourceCode":"\t\tEventJSON:        parent.EventJSON,\n\t\tnodeToolFullPath: parent.nodeToolFullPath,\n\t}\n\tcompositerc.ExprEval = compositerc.NewExpressionEvaluator(ctx)\n\n\treturn compositerc\n}\n\nfunc execAsComposite(step actionStep) common.Executor {\n\trc := step.getRunContext()\n\taction := step.getActionModel()\n\n\treturn func(ctx context.Context) error {\n\t\tcompositeRC := step.getCompositeRunContext(ctx)\n\n\t\tsteps := step.getCompositeSteps()\n\n\t\tif steps == nil || steps.main == nil {\n\t\t\treturn fmt.Errorf(\"missing steps in composite action\")\n\t\t}\n\n\t\tctx = WithCompositeLogger(ctx, &compositeRC.Masks)\n\n\t\terr := steps.main(ctx)\n\n\t\t// Map outputs from composite RunContext to job RunContext\n\t\teval := compositeRC.NewExpressionEvaluator(ctx)\n\t\tfor outputName, output := range action.Outputs {\n\t\t\trc.setOutput(ctx, map[string]string{\n\t\t\t\t\"name\": outputName,\n\t\t\t}, eval.Interpolate(ctx, output.Value))\n\t\t}\n\n\t\trc.Masks = append(rc.Masks, compositeRC.Masks...)\n\t\trc.ExtraPath = compositeRC.ExtraPath\n\t\t// compositeRC.Env is dirty, contains INPUT_ and merged step env, only rely on compositeRC.GlobalEnv\n\t\tmergeIntoMap := mergeIntoMapCaseSensitive","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/runner/action_composite.go#L76-L112","documentation":"execAsComposite (pkg/runner/action_composite.go) runs the composite action's main steps. It calls step.getCompositeRunContext(ctx) then step.getCompositeSteps(); if steps is nil or steps.main is nil it errors, because a composite action with no main steps cannot execute. steps is populated from the action model's Runs.Steps — so this means the loaded action.yml has 'using: composite' but no (or unparsable) 'runs.steps'.","triggerScenarios":"action.yml contains 'runs: using: composite' with a missing or empty 'steps:' key; steps failed to unmarshal (wrong indentation, non-list value); or the action model was loaded from an empty/incorrect file at the action path.","commonSituations":"Hand-written action.yml that forgets 'steps:' under 'runs:'; copy-paste from a Docker action leaving 'image:' but no composite steps; local action directory path wrong so an unrelated/blank action.yml is read.","solutions":["Add a non-empty 'steps:' list under 'runs:' in the composite action's action.yml.","Validate YAML structure: 'runs' must contain 'using: composite' and 'steps:' as a list of step objects.","Verify the local 'uses:' path resolves to the directory holding action.yml.","Clear cache for remote actions and re-fetch at a ref where the action.yml is correct."],"exampleFix":"# before (action.yml)\nname: my-action\nruns:\n  using: composite\n\n# after\nname: my-action\nruns:\n  using: composite\n  steps:\n    - run: echo hi\n      shell: bash","handlingStrategy":"validation","validationCode":"python3 - <<'EOF'\nimport yaml,sys\na=yaml.safe_load(open('action.yml'))\nruns=a.get('runs',{})\nassert runs.get('using')=='composite', 'not composite'\nassert isinstance(runs.get('steps'),list) and runs['steps'], 'runs.steps missing/empty'\nprint('composite action.yml ok')\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["CI-validate action.yml schema for composite actions.","Use actionlint on workflows and actions.","Ensure local uses: paths point at the action.yml directory."],"tags":["composite-action","action-yml","act","workflow"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}