From b586d80f9775c3983b0fc31bda1d759539efea4e Mon Sep 17 00:00:00 2001 From: Giteabot Date: Wed, 6 May 2026 19:31:49 -0700 Subject: [PATCH] fix(actions): prevent panic when workflow contains null jobs (#37570) (#37576) Backport #37570 by @Exgene ## The issue Closes #37568. Basically due to empty fields being present in the actions file, the jobs would be produced as `nil` inside `jobparser.go` . Because of this when we call `Parse` on the `jobparser` module. ```go Needs: job.Needs(), ``` would propagate the `nil` job down the chain. ## The fix For now i decide to fix it by guarding with an `if job == nil` check. Signed-off-by: wxiaoguang Co-authored-by: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Co-authored-by: wxiaoguang Co-authored-by: Lunny Xiao --- modules/actions/jobparser/jobparser.go | 3 +++ modules/actions/jobparser/jobparser_test.go | 17 +++++++++++++++++ .../testdata/null_job_explicit.in.yaml | 9 +++++++++ .../testdata/null_job_implicit.in.yaml | 9 +++++++++ 4 files changed, 38 insertions(+) create mode 100644 modules/actions/jobparser/testdata/null_job_explicit.in.yaml create mode 100644 modules/actions/jobparser/testdata/null_job_implicit.in.yaml diff --git a/modules/actions/jobparser/jobparser.go b/modules/actions/jobparser/jobparser.go index 1d4c4c1756..e3a99b95ac 100644 --- a/modules/actions/jobparser/jobparser.go +++ b/modules/actions/jobparser/jobparser.go @@ -31,6 +31,9 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) { } results := map[string]*JobResult{} for id, job := range origin.Jobs { + if job == nil { + return nil, fmt.Errorf("needed job not found: %q", id) + } results[id] = &JobResult{ Needs: job.Needs(), Result: pc.jobResults[id], diff --git a/modules/actions/jobparser/jobparser_test.go b/modules/actions/jobparser/jobparser_test.go index 51ba70fc2a..e74f0644f8 100644 --- a/modules/actions/jobparser/jobparser_test.go +++ b/modules/actions/jobparser/jobparser_test.go @@ -59,6 +59,13 @@ func TestParse(t *testing.T) { wantErr: false, }, } + invalidFileTests := []struct { + name string + }{ + {name: "null_job_implicit"}, + {name: "null_job_explicit"}, + } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { content := ReadTestdata(t, tt.name+".in.yaml") @@ -84,4 +91,14 @@ func TestParse(t *testing.T) { assert.Equal(t, string(want), builder.String()) }) } + + for _, tt := range invalidFileTests { + t.Run(tt.name, func(t *testing.T) { + content := ReadTestdata(t, tt.name+".in.yaml") + require.NotPanics(t, func() { + _, err := Parse(content) + require.Error(t, err) + }) + }) + } } diff --git a/modules/actions/jobparser/testdata/null_job_explicit.in.yaml b/modules/actions/jobparser/testdata/null_job_explicit.in.yaml new file mode 100644 index 0000000000..6731507a32 --- /dev/null +++ b/modules/actions/jobparser/testdata/null_job_explicit.in.yaml @@ -0,0 +1,9 @@ +# null_job_explicit.in.yaml +on: push +jobs: + empty: null + notempty: + needs: empty + runs-on: ubuntu-latest + steps: + - run: echo ok diff --git a/modules/actions/jobparser/testdata/null_job_implicit.in.yaml b/modules/actions/jobparser/testdata/null_job_implicit.in.yaml new file mode 100644 index 0000000000..26591aadcd --- /dev/null +++ b/modules/actions/jobparser/testdata/null_job_implicit.in.yaml @@ -0,0 +1,9 @@ +# null_job_implicit.in.yaml +on: push +jobs: + empty: + notempty: + needs: empty + runs-on: ubuntu-latest + steps: + - run: echo ok