nektos/act · error

Invalid run/uses syntax for job:%s step:%+v

Error message

Invalid run/uses syntax for job:%s step:%+v

What it means

The step factory could not classify the step: model.Step.Type() returned StepTypeInvalid, which happens when both `run:` and `uses:` are empty, or when both are set on the same step. The factory therefore has no step implementation to instantiate and returns this error naming the job and echoing the whole step model.

Source

Thrown at pkg/runner/step_factory.go:18

package runner

import (
	"fmt"

	"github.com/nektos/act/pkg/model"
)

type stepFactory interface {
	newStep(step *model.Step, rc *RunContext) (step, error)
}

type stepFactoryImpl struct{}

func (sf *stepFactoryImpl) newStep(stepModel *model.Step, rc *RunContext) (step, error) {
	switch stepModel.Type() {
	case model.StepTypeInvalid:
		return nil, fmt.Errorf("Invalid run/uses syntax for job:%s step:%+v", rc.Run, stepModel)
	case model.StepTypeRun:
		return &stepRun{
			Step:       stepModel,
			RunContext: rc,
		}, nil
	case model.StepTypeUsesActionLocal:
		return &stepActionLocal{
			Step:       stepModel,
			RunContext: rc,
			readAction: readActionImpl,
			runAction:  runActionImpl,
		}, nil
	case model.StepTypeUsesActionRemote:
		return &stepActionRemote{
			Step:       stepModel,
			RunContext: rc,
			readAction: readActionImpl,
			runAction:  runActionImpl,

View on GitHub (pinned to 4f41128141)

Solutions

  1. Give each step exactly one of `run:` or `uses:`.
  2. Check indentation: keys of a step must align under the same '- ' item; a misplaced run: one level up yields an empty step.
  3. Run actionlint on the workflow — it flags steps missing run/uses immediately.

Example fix

# before
- name: build
  run: make build
  uses: actions/setup-go@v5   # both set
# after
- name: build
  uses: actions/setup-go@v5
- name: build
  run: make build
Defensive patterns

Strategy: validation

Validate before calling

python3 - <<'EOF'
import yaml,sys
for f in __import__('glob').glob('.github/workflows/*.yml'):
    wf=yaml.safe_load(open(f))
    for j in wf.get('jobs',{}).values():
        for s in j.get('steps',[]):
            has_run=bool(s.get('run')); has_uses=bool(s.get('uses'))
            if has_run == has_uses:  # both or neither
                sys.exit(f"{f}: step {s.get('name')} must have exactly one of run/uses")
EOF

Type guard

func stepWellFormed(s map[string]any) bool {
  _, r := s["run"]; _, u := s["uses"]
  return r != u // exactly one present
}

Prevention

When it happens

Trigger: A step block in the workflow has neither `run:` nor `uses:`, or declares both at once; often a side effect of YAML indentation putting keys at the wrong level so the step struct ends up empty.

Common situations: Copy-paste merging two step examples leaving both keys; YAML indentation that detaches run/uses from the step map (making them job-level or ignored); a step reduced to only `name:`/`with:` after editing; template placeholders never filled in.

Related errors


AI-assisted analysis of nektos/act@4f41128141 (2026-08-15). Data as JSON: /api/errors/d47560f04599bffc. Report an issue: GitHub.