From 66a723f9a66936b6697e91476da00c1644e6b817 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 27 Apr 2026 14:05:24 -0700 Subject: [PATCH] fix: preserve job env for steps Co-Authored-By: Codet (GPT-5) --- act/runner/run_context.go | 18 +++++++++++------- act/runner/run_context_test.go | 24 ++++++++++++++++++++++++ act/runner/step_test.go | 11 +++++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/act/runner/run_context.go b/act/runner/run_context.go index 19ad88ef..1e35b957 100644 --- a/act/runner/run_context.go +++ b/act/runner/run_context.go @@ -78,15 +78,19 @@ func (rc *RunContext) String() string { // GetEnv returns the env for the context func (rc *RunContext) GetEnv() map[string]string { - if rc.Env == nil { - rc.Env = map[string]string{} - if rc.Run != nil && rc.Run.Workflow != nil && rc.Config != nil { - job := rc.Run.Job() - if job != nil { - rc.Env = mergeMaps(rc.Run.Workflow.Env, job.Environment(), rc.Config.Env) - } + baseEnv := map[string]string{} + if rc.Run != nil && rc.Run.Workflow != nil && rc.Config != nil { + job := rc.Run.Job() + if job != nil { + baseEnv = mergeMaps(rc.Run.Workflow.Env, job.Environment(), rc.Config.Env) } } + + if rc.Env == nil { + rc.Env = baseEnv + } else { + rc.Env = mergeMaps(baseEnv, rc.Env) + } rc.Env["ACT"] = "true" if !rc.Config.NoSkipCheckout { diff --git a/act/runner/run_context_test.go b/act/runner/run_context_test.go index a1e86597..8cf35130 100644 --- a/act/runner/run_context_test.go +++ b/act/runner/run_context_test.go @@ -572,6 +572,10 @@ if: false`, ""), } func TestRunContextGetEnv(t *testing.T) { + var jobEnv yaml.Node + err := jobEnv.Encode(map[string]string{"JOB_ONLY": "job-value"}) + assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act + tests := []struct { description string rc *RunContext @@ -612,6 +616,26 @@ func TestRunContextGetEnv(t *testing.T) { targetEnv: "OVERWRITTEN", want: "false", }, + { + description: "Pre-populated run context env should still include job env", + rc: &RunContext{ + Config: &Config{}, + Env: map[string]string{ + "RUNTIME_ONLY": "true", + }, + Run: &model.Run{ + JobID: "test", + Workflow: &model.Workflow{ + Jobs: map[string]*model.Job{"test": { + Name: "test", + Env: jobEnv, + }}, + }, + }, + }, + targetEnv: "JOB_ONLY", + want: "job-value", + }, } for _, test := range tests { diff --git a/act/runner/step_test.go b/act/runner/step_test.go index 7f4d93a4..e3484118 100644 --- a/act/runner/step_test.go +++ b/act/runner/step_test.go @@ -120,6 +120,10 @@ func TestSetupEnv(t *testing.T) { cm := &containerMock{} sm := &stepMock{} + var jobEnv yaml.Node + err := jobEnv.Encode(map[string]string{"JOB_KEY": "jobvalue"}) + assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act + rc := &RunContext{ Config: &Config{ Env: map[string]string{ @@ -131,9 +135,7 @@ func TestSetupEnv(t *testing.T) { Workflow: &model.Workflow{ Jobs: map[string]*model.Job{ "1": { - Env: yaml.Node{ - Value: "JOB_KEY: jobvalue", - }, + Env: jobEnv, }, }, }, @@ -155,7 +157,7 @@ func TestSetupEnv(t *testing.T) { sm.On("getStepModel").Return(step) sm.On("getEnv").Return(&env) - err := setupEnv(context.Background(), sm) + err = setupEnv(context.Background(), sm) assert.Nil(t, err) //nolint:testifylint // pre-existing issue from nektos/act // These are commit or system specific @@ -191,6 +193,7 @@ func TestSetupEnv(t *testing.T) { "GITHUB_SERVER_URL": "https://", "GITHUB_WORKFLOW": "", "INPUT_STEP_WITH": "with-value", + "JOB_KEY": "jobvalue", "RC_KEY": "rcvalue", "RUNNER_PERFLOG": "/dev/null", "RUNNER_TRACKING_ID": "",