{"record":{"id":"9449b3af3be4a1d6","repo":"nektos/act","slug":"unable-to-build-dependency-graph-for-s-s","errorCode":null,"errorMessage":"unable to build dependency graph for %s (%s)","messagePattern":"unable to build dependency graph for (.+?) \\((.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/model/planner.go","lineNumber":375,"sourceCode":"\t\tjobIDs = newJobIDs\n\t}\n\n\t// next, build an execution graph\n\tstages := make([]*Stage, 0)\n\tfor len(jobDependencies) > 0 {\n\t\tstage := new(Stage)\n\t\tfor jID, jDeps := range jobDependencies {\n\t\t\t// make sure all deps are in the graph already\n\t\t\tif listInStages(jDeps, stages...) {\n\t\t\t\tstage.Runs = append(stage.Runs, &Run{\n\t\t\t\t\tWorkflow: w,\n\t\t\t\t\tJobID:    jID,\n\t\t\t\t})\n\t\t\t\tdelete(jobDependencies, jID)\n\t\t\t}\n\t\t}\n\t\tif len(stage.Runs) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"unable to build dependency graph for %s (%s)\", w.Name, w.File)\n\t\t}\n\t\tstages = append(stages, stage)\n\t}\n\n\treturn stages, nil\n}\n\n// return true iff all strings in srcList exist in at least one of the stages\nfunc listInStages(srcList []string, stages ...*Stage) bool {\n\tfor _, src := range srcList {\n\t\tfound := false\n\t\tfor _, stage := range stages {\n\t\t\tfor _, search := range stage.GetJobIDs() {\n\t\t\t\tif src == search {\n\t\t\t\t\tfound = true\n\t\t\t\t}\n\t\t\t}\n\t\t}","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/model/planner.go#L357-L393","documentation":"PlanJobs builds stages by repeatedly moving jobs whose dependencies are all satisfied into the next stage. If a pass adds zero runs but jobs remain, some job's `needs:` references either do not exist or form a cycle, and planning aborts. The message names the workflow (name and file) where the graph is unresolvable.","triggerScenarios":"A job declares `needs: deplooy` (typo of deploy); a cycle like a→b→a; or a job depending on a job filtered out by the event/condition so it never enters a stage.","commonSituations":"Renaming a job without updating dependents' needs; copy-paste workflows introducing cyclic needs; references to jobs that only run on a different event.","solutions":["Check the workflow file named in the message; verify every `needs:` entry matches an existing job ID exactly (case-sensitive).","Break any dependency cycle by removing or reordering one needs edge.","If a dependency is event-conditional, either make the dependent job conditional the same way or restructure so all scheduled jobs are plannable."],"exampleFix":"# before\njobs:\n  build:\n    needs: deplooy\n# after\njobs:\n  build:\n    needs: deploy","handlingStrategy":"validation","validationCode":"func validateNeeds(wf *Workflow) error {\n    ids := map[string]bool{}\n    for id := range wf.Jobs { ids[id] = true }\n    for id, j := range wf.Jobs {\n        for _, dep := range j.Needs {\n            if !ids[dep] { return fmt.Errorf(\"job %q needs unknown job %q\", id, dep) }\n        }\n    }\n    // additionally run a DFS cycle check over the needs edges\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"unable to build dependency graph\") {\n    // the message names the workflow file; inspect its needs: edges for typos/cycles\n}","preventionTips":["After renaming a job, grep the workflow for the old ID in needs: blocks.","Keep dependency graphs acyclic by construction; draw them out for complex pipelines.","Lint workflows with actionlint, which detects both unknown needs and cycles."],"tags":["workflow","planner","dependency-graph","needs"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}