{"record":{"id":"b72ac1387c86aaf0","repo":"nektos/act","slug":"workflow-is-not-valid-s-job-name-s-is-inva","errorCode":null,"errorMessage":"workflow is not valid. '%s': Job name '%s' is invalid. Names must start with a letter or '_' and contain only alphanumeric characters, '-', or '_'","messagePattern":"workflow is not valid\\. '(.+?)': Job name '(.+?)' is invalid\\. Names must start with a letter or '_' and contain only alphanumeric characters, '-', or '_'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/model/planner.go","lineNumber":190,"sourceCode":"\tif workflow.Name == \"\" {\n\t\tworkflow.Name = name\n\t}\n\n\terr = validateJobName(workflow)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twp.workflows = append(wp.workflows, workflow)\n\n\treturn wp, nil\n}\n\nfunc validateJobName(workflow *Workflow) error {\n\tjobNameRegex := regexp.MustCompile(`^([[:alpha:]_][[:alnum:]_\\-]*)$`)\n\tfor k := range workflow.Jobs {\n\t\tif ok := jobNameRegex.MatchString(k); !ok {\n\t\t\treturn 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)\n\t\t}\n\t}\n\treturn nil\n}\n\ntype workflowPlanner struct {\n\tworkflows []*Workflow\n}\n\n// PlanEvent builds a new list of runs to execute in parallel for an event name\nfunc (wp *workflowPlanner) PlanEvent(eventName string) (*Plan, error) {\n\tplan := new(Plan)\n\tif len(wp.workflows) == 0 {\n\t\tlog.Debug(\"no workflows found by planner\")\n\t\treturn plan, nil\n\t}\n\tvar lastErr error\n","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/model/planner.go#L172-L208","documentation":"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.","triggerScenarios":"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:`.","commonSituations":"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.","solutions":["Rename the job key to start with a letter or underscore and use only [A-Za-z0-9_-].","Move the human-readable label to `name:` under the job.","Update every `needs:` reference and `jobs.<id>.outputs` consumer to the new ID."],"exampleFix":"# before\njobs:\n  1-deploy:\n    name: Deploy prod\n# after\njobs:\n  deploy-prod:\n    name: 1-deploy (Deploy prod)","handlingStrategy":"validation","validationCode":"var jobIDRe = regexp.MustCompile(`^[[:alpha:]_][[:alnum:]_-]*$`)\nfor id := range wf.Jobs {\n    if !jobIDRe.MatchString(id) {\n        return fmt.Errorf(\"invalid job id %q\", id)\n    }\n}","typeGuard":"func isValidJobID(id string) bool {\n    if id == \"\" { return false }\n    for i, r := range id {\n        switch {\n        case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r == '_':\n        case r >= '0' && r <= '9', r == '-':\n            if i == 0 { return false }\n        default:\n            return false\n        }\n    }\n    return true\n}","tryCatchPattern":null,"preventionTips":["Use only [a-z0-9_-] job IDs starting with a letter or underscore.","Put display strings in `name:`, never in the job key.","Update all `needs:` references after renaming a job ID."],"tags":["workflow","job-id","validation","regex"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}