{"record":{"id":"7a59504d0ed47e48","repo":"nektos/act","slug":"invalid-step-v-missing-run-or-uses-key","errorCode":null,"errorMessage":"invalid Step %v: missing run or uses key","messagePattern":"invalid Step (.+?): missing run or uses key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/runner/job_executor.go","lineNumber":69,"sourceCode":"\t\t\trc.Env[k] = rc.ExprEval.Interpolate(ctx, v)\n\t\t}\n\t\treturn nil\n\t})\n\n\tvar setJobError = func(ctx context.Context, err error) error {\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t\tlogger := common.Logger(ctx)\n\t\tlogger.Errorf(\"%v\", err)\n\t\tcommon.SetJobError(ctx, err)\n\t\treturn err\n\t}\n\n\tfor i, stepModel := range infoSteps {\n\t\tif stepModel == nil {\n\t\t\treturn func(_ context.Context) error {\n\t\t\t\treturn fmt.Errorf(\"invalid Step %v: missing run or uses key\", i)\n\t\t\t}\n\t\t}\n\t\tif stepModel.ID == \"\" {\n\t\t\tstepModel.ID = fmt.Sprintf(\"%d\", i)\n\t\t}\n\n\t\tstep, err := sf.newStep(stepModel, rc)\n\n\t\tif err != nil {\n\t\t\treturn common.NewErrorExecutor(err)\n\t\t}\n\n\t\tpreSteps = append(preSteps, useStepLogger(rc, stepModel, stepStagePre, step.pre().ThenError(setJobError)))\n\n\t\tstepExec := step.main()\n\t\tsteps = append(steps, useStepLogger(rc, stepModel, stepStageMain, func(ctx context.Context) error {\n\t\t\terr := stepExec(ctx)\n\t\t\tif err != nil {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/runner/job_executor.go#L51-L87","documentation":"In the job executor's step-building loop (pkg/runner/job_executor.go), act iterates infoSteps produced by splitting each step's 'run'/'uses' interpolation. If a step model comes back nil, the step had neither a 'run:' nor a 'uses:' key after parsing — the executor factory inserted a nil placeholder — and this error names the offending step index.","triggerScenarios":"A workflow job step that has neither 'run' nor 'uses' (e.g. only 'name:' and 'if:'), or a step whose keys are misindented so the parser sees an empty step entry. The index i is the 0-based position in the job's steps list.","commonSituations":"Copy-paste where the 'run:' line was deleted; YAML indentation making 'run' a child of another key; placeholder/commented-out step bodies.","solutions":["Go to the job's steps list; the failing step is at the printed index (0-based). Add a valid 'run:' or 'uses:' to it, or delete the step.","Run yamllint/actionlint on the workflow to catch structure errors.","Check indentation: 'run'/'uses' must be direct children of the step map item."],"exampleFix":"# before\nsteps:\n  - name: build\n    if: always()\n\n# after\nsteps:\n  - name: build\n    if: always()\n    run: make build","handlingStrategy":"validation","validationCode":"python3 - <<'EOF'\nimport yaml,glob\nfor f in glob.glob('.github/workflows/*.y*ml'):\n    y=yaml.safe_load(open(f))\n    for jid,job in (y.get('jobs') or {}).items():\n        for i,s in enumerate(job.get('steps') or []):\n            if s is None or not ('run' in s or 'uses' in s):\n                raise SystemExit(f'{f}: job {jid} step index {i} has no run/uses key')\nprint('steps ok')\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run actionlint in CI; it flags steps without run/uses.","The error's index is 0-based — count steps from zero to find the offender.","Watch YAML indentation when editing steps."],"tags":["workflow","yaml","steps","act"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}