From 5f2c3bbffadb00410d2597c54987f3266b77e1f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 08:01:31 +0000 Subject: [PATCH 1/8] Initial plan From 89dbca89f49b150ff2cc970f791e2a852c5375f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 08:15:19 +0000 Subject: [PATCH 2/8] fix: guard conclude_threat_detection.sh against threat-detect when RUN_DETECTION=false Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/sh/conclude_threat_detection.sh | 8 +++- .../threat_detection_conclude_script_test.go | 46 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/actions/setup/sh/conclude_threat_detection.sh b/actions/setup/sh/conclude_threat_detection.sh index e3159664639..d515d64b168 100755 --- a/actions/setup/sh/conclude_threat_detection.sh +++ b/actions/setup/sh/conclude_threat_detection.sh @@ -10,7 +10,13 @@ DETECTION_STATUS_PREFIX="THREAT_DETECTION_STATUS:" continue_on_error="${GH_AW_DETECTION_CONTINUE_ON_ERROR:-true}" continue_on_error="$(echo "${continue_on_error}" | tr '[:upper:]' '[:lower:]')" -if [ "${RUN_DETECTION:-false}" = "true" ] && [ ! -f "${RESULT_FILE}" ]; then +if [ "${RUN_DETECTION:-false}" != "true" ]; then + echo "conclusion=skipped" >> "${GITHUB_OUTPUT}" + echo "GH_AW_DETECTION_CONCLUSION=skipped" >> "${GITHUB_ENV}" + exit 0 +fi + +if [ ! -f "${RESULT_FILE}" ]; then detection_status="" if [ -f "${DETECTION_LOG_FILE}" ]; then detection_status="$(grep "${DETECTION_STATUS_PREFIX}" "${DETECTION_LOG_FILE}" | tail -n 1 || true)" diff --git a/pkg/workflow/threat_detection_conclude_script_test.go b/pkg/workflow/threat_detection_conclude_script_test.go index 902fd51d027..360a685f474 100644 --- a/pkg/workflow/threat_detection_conclude_script_test.go +++ b/pkg/workflow/threat_detection_conclude_script_test.go @@ -108,6 +108,52 @@ func TestConcludeThreatDetectionScript_MissingResultSurfacesDetectorStatus(t *te } } +func TestConcludeThreatDetectionScript_SkippedWhenRunDetectionFalse(t *testing.T) { + tmpDir := t.TempDir() + scriptPath := filepath.Join("..", "..", "actions", "setup", "sh", "conclude_threat_detection.sh") + outputFile := filepath.Join(tmpDir, "github_output.txt") + envFile := filepath.Join(tmpDir, "github_env.txt") + // No result file written — threat-detect is not installed — both should be irrelevant. + missingResult := filepath.Join(tmpDir, "missing_detection_result.json") + + // Use a PATH that does not include threat-detect by prepending a + // sentinel bin dir that contains no executables. The guard must + // exit before ever reaching the threat-detect invocation. + emptyBinDir := filepath.Join(tmpDir, "empty-bin") + if err := os.MkdirAll(emptyBinDir, 0755); err != nil { + t.Fatalf("failed to create empty bin dir: %v", err) + } + + cmd := exec.Command("bash", scriptPath, missingResult) + cmd.Env = append(os.Environ(), + "RUN_DETECTION=false", + "GITHUB_OUTPUT="+outputFile, + "GITHUB_ENV="+envFile, + "PATH="+emptyBinDir+":"+os.Getenv("PATH"), + ) + + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("script should exit 0 when RUN_DETECTION=false: %v\nOutput: %s", err, out) + } + + outputData, err := os.ReadFile(outputFile) + if err != nil { + t.Fatalf("failed to read GITHUB_OUTPUT: %v", err) + } + if !strings.Contains(string(outputData), "conclusion=skipped") { + t.Fatalf("expected conclusion=skipped in GITHUB_OUTPUT, got: %s", outputData) + } + + envData, err := os.ReadFile(envFile) + if err != nil { + t.Fatalf("failed to read GITHUB_ENV: %v", err) + } + if !strings.Contains(string(envData), "GH_AW_DETECTION_CONCLUSION=skipped") { + t.Fatalf("expected GH_AW_DETECTION_CONCLUSION=skipped in GITHUB_ENV, got: %s", envData) + } +} + func TestConcludeThreatDetectionScript_InvokesThreatDetectConclude(t *testing.T) { tmpDir := t.TempDir() scriptPath := filepath.Join("..", "..", "actions", "setup", "sh", "conclude_threat_detection.sh") From 62eaccaae3b1d67643e56658e884fe9814285348 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:25:54 +0000 Subject: [PATCH 3/8] fix: emit success=true and reason= on skipped detection path to preserve output contract Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/sh/conclude_threat_detection.sh | 3 +++ .../threat_detection_conclude_script_test.go | 19 +++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/actions/setup/sh/conclude_threat_detection.sh b/actions/setup/sh/conclude_threat_detection.sh index d515d64b168..0863d232731 100755 --- a/actions/setup/sh/conclude_threat_detection.sh +++ b/actions/setup/sh/conclude_threat_detection.sh @@ -12,7 +12,10 @@ continue_on_error="$(echo "${continue_on_error}" | tr '[:upper:]' '[:lower:]')" if [ "${RUN_DETECTION:-false}" != "true" ]; then echo "conclusion=skipped" >> "${GITHUB_OUTPUT}" + echo "success=true" >> "${GITHUB_OUTPUT}" + echo "reason=" >> "${GITHUB_OUTPUT}" echo "GH_AW_DETECTION_CONCLUSION=skipped" >> "${GITHUB_ENV}" + echo "GH_AW_DETECTION_REASON=" >> "${GITHUB_ENV}" exit 0 fi diff --git a/pkg/workflow/threat_detection_conclude_script_test.go b/pkg/workflow/threat_detection_conclude_script_test.go index 360a685f474..110cd70dba2 100644 --- a/pkg/workflow/threat_detection_conclude_script_test.go +++ b/pkg/workflow/threat_detection_conclude_script_test.go @@ -141,16 +141,27 @@ func TestConcludeThreatDetectionScript_SkippedWhenRunDetectionFalse(t *testing.T if err != nil { t.Fatalf("failed to read GITHUB_OUTPUT: %v", err) } - if !strings.Contains(string(outputData), "conclusion=skipped") { - t.Fatalf("expected conclusion=skipped in GITHUB_OUTPUT, got: %s", outputData) + outputText := string(outputData) + if !strings.Contains(outputText, "conclusion=skipped") { + t.Fatalf("expected conclusion=skipped in GITHUB_OUTPUT, got: %s", outputText) + } + if !strings.Contains(outputText, "success=true") { + t.Fatalf("expected success=true in GITHUB_OUTPUT, got: %s", outputText) + } + if !strings.Contains(outputText, "reason=") { + t.Fatalf("expected reason= in GITHUB_OUTPUT, got: %s", outputText) } envData, err := os.ReadFile(envFile) if err != nil { t.Fatalf("failed to read GITHUB_ENV: %v", err) } - if !strings.Contains(string(envData), "GH_AW_DETECTION_CONCLUSION=skipped") { - t.Fatalf("expected GH_AW_DETECTION_CONCLUSION=skipped in GITHUB_ENV, got: %s", envData) + envText := string(envData) + if !strings.Contains(envText, "GH_AW_DETECTION_CONCLUSION=skipped") { + t.Fatalf("expected GH_AW_DETECTION_CONCLUSION=skipped in GITHUB_ENV, got: %s", envText) + } + if !strings.Contains(envText, "GH_AW_DETECTION_REASON=") { + t.Fatalf("expected GH_AW_DETECTION_REASON= in GITHUB_ENV, got: %s", envText) } } From 07574be965b4658b83c5bc37349aaadc54549998 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:58:45 +0000 Subject: [PATCH 4/8] fix: remove GITHUB_ENV writes from skipped detection guard, use output only Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/sh/conclude_threat_detection.sh | 2 -- .../threat_detection_conclude_script_test.go | 14 -------------- 2 files changed, 16 deletions(-) diff --git a/actions/setup/sh/conclude_threat_detection.sh b/actions/setup/sh/conclude_threat_detection.sh index 0863d232731..c4e685be41b 100755 --- a/actions/setup/sh/conclude_threat_detection.sh +++ b/actions/setup/sh/conclude_threat_detection.sh @@ -14,8 +14,6 @@ if [ "${RUN_DETECTION:-false}" != "true" ]; then echo "conclusion=skipped" >> "${GITHUB_OUTPUT}" echo "success=true" >> "${GITHUB_OUTPUT}" echo "reason=" >> "${GITHUB_OUTPUT}" - echo "GH_AW_DETECTION_CONCLUSION=skipped" >> "${GITHUB_ENV}" - echo "GH_AW_DETECTION_REASON=" >> "${GITHUB_ENV}" exit 0 fi diff --git a/pkg/workflow/threat_detection_conclude_script_test.go b/pkg/workflow/threat_detection_conclude_script_test.go index 110cd70dba2..3430422b3dc 100644 --- a/pkg/workflow/threat_detection_conclude_script_test.go +++ b/pkg/workflow/threat_detection_conclude_script_test.go @@ -112,7 +112,6 @@ func TestConcludeThreatDetectionScript_SkippedWhenRunDetectionFalse(t *testing.T tmpDir := t.TempDir() scriptPath := filepath.Join("..", "..", "actions", "setup", "sh", "conclude_threat_detection.sh") outputFile := filepath.Join(tmpDir, "github_output.txt") - envFile := filepath.Join(tmpDir, "github_env.txt") // No result file written — threat-detect is not installed — both should be irrelevant. missingResult := filepath.Join(tmpDir, "missing_detection_result.json") @@ -128,7 +127,6 @@ func TestConcludeThreatDetectionScript_SkippedWhenRunDetectionFalse(t *testing.T cmd.Env = append(os.Environ(), "RUN_DETECTION=false", "GITHUB_OUTPUT="+outputFile, - "GITHUB_ENV="+envFile, "PATH="+emptyBinDir+":"+os.Getenv("PATH"), ) @@ -151,18 +149,6 @@ func TestConcludeThreatDetectionScript_SkippedWhenRunDetectionFalse(t *testing.T if !strings.Contains(outputText, "reason=") { t.Fatalf("expected reason= in GITHUB_OUTPUT, got: %s", outputText) } - - envData, err := os.ReadFile(envFile) - if err != nil { - t.Fatalf("failed to read GITHUB_ENV: %v", err) - } - envText := string(envData) - if !strings.Contains(envText, "GH_AW_DETECTION_CONCLUSION=skipped") { - t.Fatalf("expected GH_AW_DETECTION_CONCLUSION=skipped in GITHUB_ENV, got: %s", envText) - } - if !strings.Contains(envText, "GH_AW_DETECTION_REASON=") { - t.Fatalf("expected GH_AW_DETECTION_REASON= in GITHUB_ENV, got: %s", envText) - } } func TestConcludeThreatDetectionScript_InvokesThreatDetectConclude(t *testing.T) { From 96321410970e8aa3118db20d5de58958a95fd7a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:34:30 +0000 Subject: [PATCH 5/8] fix: remove all GITHUB_ENV writes from conclude_threat_detection.sh Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/sh/conclude_threat_detection.sh | 2 -- .../threat_detection_conclude_script_test.go | 28 ------------------- 2 files changed, 30 deletions(-) diff --git a/actions/setup/sh/conclude_threat_detection.sh b/actions/setup/sh/conclude_threat_detection.sh index c4e685be41b..8920b5f665b 100755 --- a/actions/setup/sh/conclude_threat_detection.sh +++ b/actions/setup/sh/conclude_threat_detection.sh @@ -37,8 +37,6 @@ if [ ! -f "${RESULT_FILE}" ]; then echo "conclusion=warning" >> "${GITHUB_OUTPUT}" echo "success=false" >> "${GITHUB_OUTPUT}" echo "reason=agent_failure" >> "${GITHUB_OUTPUT}" - echo "GH_AW_DETECTION_CONCLUSION=warning" >> "${GITHUB_ENV}" - echo "GH_AW_DETECTION_REASON=agent_failure" >> "${GITHUB_ENV}" exit 0 fi echo "ERR_SYSTEM: ❌ ${result_message}" diff --git a/pkg/workflow/threat_detection_conclude_script_test.go b/pkg/workflow/threat_detection_conclude_script_test.go index 3430422b3dc..a0dd06d70a4 100644 --- a/pkg/workflow/threat_detection_conclude_script_test.go +++ b/pkg/workflow/threat_detection_conclude_script_test.go @@ -13,7 +13,6 @@ const detectorStatusLine = "THREAT_DETECTION_STATUS: reason=engine_error exit=2" func TestConcludeThreatDetectionScript_MissingResultContinueOnError(t *testing.T) { scriptPath := filepath.Join("..", "..", "actions", "setup", "sh", "conclude_threat_detection.sh") outputFile := filepath.Join(t.TempDir(), "github_output.txt") - envFile := filepath.Join(t.TempDir(), "github_env.txt") missingResult := filepath.Join(t.TempDir(), "missing_detection_result.json") cmd := exec.Command("bash", scriptPath, missingResult) @@ -22,7 +21,6 @@ func TestConcludeThreatDetectionScript_MissingResultContinueOnError(t *testing.T "DETECTION_AGENTIC_EXECUTION_OUTCOME=failure", "GH_AW_DETECTION_CONTINUE_ON_ERROR=TRUE", "GITHUB_OUTPUT="+outputFile, - "GITHUB_ENV="+envFile, ) out, err := cmd.CombinedOutput() @@ -44,18 +42,6 @@ func TestConcludeThreatDetectionScript_MissingResultContinueOnError(t *testing.T if !strings.Contains(outputText, "reason=agent_failure") { t.Fatalf("expected reason=agent_failure in GITHUB_OUTPUT, got: %s", outputText) } - - envData, err := os.ReadFile(envFile) - if err != nil { - t.Fatalf("failed to read GITHUB_ENV: %v", err) - } - envText := string(envData) - if !strings.Contains(envText, "GH_AW_DETECTION_CONCLUSION=warning") { - t.Fatalf("expected warning conclusion in GITHUB_ENV, got: %s", envText) - } - if !strings.Contains(envText, "GH_AW_DETECTION_REASON=agent_failure") { - t.Fatalf("expected agent_failure reason in GITHUB_ENV, got: %s", envText) - } if !strings.Contains(string(out), "continuing because GH_AW_DETECTION_CONTINUE_ON_ERROR=true") { t.Fatalf("expected warning message about continue-on-error, got: %s", out) } @@ -65,7 +51,6 @@ func TestConcludeThreatDetectionScript_MissingResultSurfacesDetectorStatus(t *te tempDir := t.TempDir() scriptPath := filepath.Join("..", "..", "actions", "setup", "sh", "conclude_threat_detection.sh") outputFile := filepath.Join(tempDir, "github_output.txt") - envFile := filepath.Join(tempDir, "github_env.txt") missingResult := filepath.Join(tempDir, "missing_detection_result.json") detectionLog := filepath.Join(tempDir, "detection.log") @@ -80,7 +65,6 @@ func TestConcludeThreatDetectionScript_MissingResultSurfacesDetectorStatus(t *te "GH_AW_DETECTION_CONTINUE_ON_ERROR=true", "DETECTION_LOG_FILE="+detectionLog, "GITHUB_OUTPUT="+outputFile, - "GITHUB_ENV="+envFile, ) out, err := cmd.CombinedOutput() @@ -94,18 +78,6 @@ func TestConcludeThreatDetectionScript_MissingResultSurfacesDetectorStatus(t *te if !strings.Contains(string(out), "execution outcome: failure") { t.Fatalf("expected warning output to include execution outcome, got: %s", out) } - - envData, err := os.ReadFile(envFile) - if err != nil { - t.Fatalf("failed to read GITHUB_ENV: %v", err) - } - envText := string(envData) - if !strings.Contains(envText, "GH_AW_DETECTION_CONCLUSION=warning") { - t.Fatalf("expected warning conclusion in GITHUB_ENV, got: %s", envText) - } - if !strings.Contains(envText, "GH_AW_DETECTION_REASON=agent_failure") { - t.Fatalf("expected agent_failure reason in GITHUB_ENV, got: %s", envText) - } } func TestConcludeThreatDetectionScript_SkippedWhenRunDetectionFalse(t *testing.T) { From a2aff726b592b4fd4e8e47257de68c57689e370a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:45:18 +0000 Subject: [PATCH 6/8] chore: recompile smoke-call-workflow to restore actions: read on call-smoke-workflow-call Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/workflows/smoke-call-workflow.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-call-workflow.lock.yml b/.github/workflows/smoke-call-workflow.lock.yml index b7fb052fab3..fbe4cdd2d92 100644 --- a/.github/workflows/smoke-call-workflow.lock.yml +++ b/.github/workflows/smoke-call-workflow.lock.yml @@ -1112,7 +1112,7 @@ jobs: # Imported from called workflow "smoke-workflow-call" because GitHub requires the caller job to grant permissions requested by reusable workflow jobs. # Review the called workflow's job-level permissions in ./.github/workflows/smoke-workflow-call.lock.yml. permissions: - actions: write + actions: read contents: read issues: write pull-requests: write From 9f15eee988d7afafd8123132cf8a9b3f8cf8611f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 18:59:01 +0000 Subject: [PATCH 7/8] test: add SkippedWhenRunDetectionUnset to cover the default guard behaviour Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .../threat_detection_conclude_script_test.go | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/pkg/workflow/threat_detection_conclude_script_test.go b/pkg/workflow/threat_detection_conclude_script_test.go index a0dd06d70a4..d3274648150 100644 --- a/pkg/workflow/threat_detection_conclude_script_test.go +++ b/pkg/workflow/threat_detection_conclude_script_test.go @@ -123,6 +123,54 @@ func TestConcludeThreatDetectionScript_SkippedWhenRunDetectionFalse(t *testing.T } } +func TestConcludeThreatDetectionScript_SkippedWhenRunDetectionUnset(t *testing.T) { + tmpDir := t.TempDir() + scriptPath := filepath.Join("..", "..", "actions", "setup", "sh", "conclude_threat_detection.sh") + outputFile := filepath.Join(tmpDir, "github_output.txt") + missingResult := filepath.Join(tmpDir, "missing_detection_result.json") + + emptyBinDir := filepath.Join(tmpDir, "empty-bin") + if err := os.MkdirAll(emptyBinDir, 0755); err != nil { + t.Fatalf("failed to create empty bin dir: %v", err) + } + + // Build an environment that does NOT include RUN_DETECTION at all, + // verifying that the ${RUN_DETECTION:-false} default also triggers the guard. + filteredEnv := make([]string, 0, len(os.Environ())) + for _, e := range os.Environ() { + if !strings.HasPrefix(e, "RUN_DETECTION=") { + filteredEnv = append(filteredEnv, e) + } + } + filteredEnv = append(filteredEnv, + "GITHUB_OUTPUT="+outputFile, + "PATH="+emptyBinDir+":"+os.Getenv("PATH"), + ) + + cmd := exec.Command("bash", scriptPath, missingResult) + cmd.Env = filteredEnv + + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("script should exit 0 when RUN_DETECTION is unset: %v\nOutput: %s", err, out) + } + + outputData, err := os.ReadFile(outputFile) + if err != nil { + t.Fatalf("failed to read GITHUB_OUTPUT: %v", err) + } + outputText := string(outputData) + if !strings.Contains(outputText, "conclusion=skipped") { + t.Fatalf("expected conclusion=skipped in GITHUB_OUTPUT, got: %s", outputText) + } + if !strings.Contains(outputText, "success=true") { + t.Fatalf("expected success=true in GITHUB_OUTPUT, got: %s", outputText) + } + if !strings.Contains(outputText, "reason=") { + t.Fatalf("expected reason= in GITHUB_OUTPUT, got: %s", outputText) + } +} + func TestConcludeThreatDetectionScript_InvokesThreatDetectConclude(t *testing.T) { tmpDir := t.TempDir() scriptPath := filepath.Join("..", "..", "actions", "setup", "sh", "conclude_threat_detection.sh") From aea69fc70d0d35e0304fa36595f0e4e3f7bc1ddd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 07:52:24 +0000 Subject: [PATCH 8/8] docs: remove stale GITHUB_ENV export claim from buildExternalDetectorConcludeStep comment Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/threat_detection_external.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/workflow/threat_detection_external.go b/pkg/workflow/threat_detection_external.go index 7c4c8f32ca7..b313a2f4dbd 100644 --- a/pkg/workflow/threat_detection_external.go +++ b/pkg/workflow/threat_detection_external.go @@ -395,8 +395,9 @@ func (c *Compiler) buildUploadDetectionArtifactStep(data *WorkflowData) []string // buildExternalDetectorConcludeStep creates the conclude step for the external // threat-detect binary. It runs `threat-detect conclude --result-file ...` which reads // the structured detection_result.json and sets the detection_conclusion/detection_reason/ -// detection_success step outputs and exports GH_AW_DETECTION_CONCLUSION/GH_AW_DETECTION_REASON, -// preserving the same gate contract as the inline parse_threat_detection_results.cjs path. +// detection_success step outputs, preserving the same gate contract as the inline +// parse_threat_detection_results.cjs path. Outputs (not env vars) are used exclusively; +// downstream jobs consume these via needs.detection.outputs.* expressions. // The step ID (detection_conclusion) and env vars (RUN_DETECTION, DETECTION_AGENTIC_EXECUTION_OUTCOME, // GH_AW_DETECTION_CONTINUE_ON_ERROR) are byte-identical to the inline conclude step. func (c *Compiler) buildExternalDetectorConcludeStep(data *WorkflowData) []string {