nektos/act · error

workflow is not valid. '%s': Job name '%s' is invalid. Names

Error message

workflow is not valid. '%s': Job name '%s' is invalid. Names must start with a letter or '_' and contain only alphanumeric characters, '-', or '_'

What it means

validateJobName rejects a job ID that does not match ^([[:alpha:]_][[:alnum:]_-]*)$ — it must start with a letter or underscore and contain only letters, digits, underscores, and hyphens. This mirrors GitHub's job-ID rules because job IDs become `needs` references and step outputs are keyed off them.

Source

Thrown at pkg/model/planner.go:190

	if workflow.Name == "" {
		workflow.Name = name
	}

	err = validateJobName(workflow)
	if err != nil {
		return nil, err
	}

	wp.workflows = append(wp.workflows, workflow)

	return wp, nil
}

func validateJobName(workflow *Workflow) error {
	jobNameRegex := regexp.MustCompile(`^([[:alpha:]_][[:alnum:]_\-]*)$`)
	for k := range workflow.Jobs {
		if ok := jobNameRegex.MatchString(k); !ok {
			return fmt.Errorf("workflow is not valid. '%s': Job name '%s' is invalid. Names must start with a letter or '_' and contain only alphanumeric characters, '-', or '_'", workflow.Name, k)
		}
	}
	return nil
}

type workflowPlanner struct {
	workflows []*Workflow
}

// PlanEvent builds a new list of runs to execute in parallel for an event name
func (wp *workflowPlanner) PlanEvent(eventName string) (*Plan, error) {
	plan := new(Plan)
	if len(wp.workflows) == 0 {
		log.Debug("no workflows found by planner")
		return plan, nil
	}
	var lastErr error

View on GitHub (pinned to 4f41128141)

Solutions

  1. Rename the job key to start with a letter or underscore and use only [A-Za-z0-9_-].
  2. Move the human-readable label to `name:` under the job.
  3. Update every `needs:` reference and `jobs.<id>.outputs` consumer to the new ID.

Example fix

# before
jobs:
  1-deploy:
    name: Deploy prod
# after
jobs:
  deploy-prod:
    name: 1-deploy (Deploy prod)
Defensive patterns

Strategy: validation

Validate before calling

var jobIDRe = regexp.MustCompile(`^[[:alpha:]_][[:alnum:]_-]*$`)
for id := range wf.Jobs {
    if !jobIDRe.MatchString(id) {
        return fmt.Errorf("invalid job id %q", id)
    }
}

Type guard

func isValidJobID(id string) bool {
    if id == "" { return false }
    for i, r := range id {
        switch {
        case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r == '_':
        case r >= '0' && r <= '9', r == '-':
            if i == 0 { return false }
        default:
            return false
        }
    }
    return true
}

Prevention

When it happens

Trigger: A jobs: key like `1-deploy` (starts with digit), `my.job` (dot), `build test` (space), or `build!` (punctuation). The map key under `jobs:` is checked, not the job's display `name:`.

Common situations: Auto-generated job IDs from scaffolding tools that include dots/spaces; renaming jobs to match ticket numbers; confusion between `name:` (any string allowed) and the job ID key.

Related errors


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