From e57b4d3285c42855ee0f7ef6f792e8ed30fd2e17 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 01:09:18 +0000 Subject: [PATCH 1/3] Initial plan From a6d69415c22c31ea0c6c8efed7d417d9e9532105 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 01:32:12 +0000 Subject: [PATCH 2/3] fix: gate push_repo_memory on agent not skipped instead of success MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the push_repo_memory job condition from checking needs.agent.result == 'success' to needs.agent.result != 'skipped'. This prevents push_repo_memory from running on no-op workflow invocations (e.g. bot comments where pre_activation is skipped and the skip cascades through activation → agent → detection). The != 'skipped' check also allows repo-memory to be pushed when the agent fails, preserving partial memory data. Fixes #1556 Agent-Logs-Url: https://github.com/github/gh-aw/sessions/053233b6-7afc-4caf-bf6e-74bbd9f26562 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../agent-performance-analyzer.lock.yml | 2 +- .github/workflows/audit-workflows.lock.yml | 2 +- .../workflows/code-scanning-fixer.lock.yml | 2 +- .../workflows/copilot-agent-analysis.lock.yml | 2 +- .../copilot-cli-deep-research.lock.yml | 2 +- .../copilot-pr-nlp-analysis.lock.yml | 2 +- .../copilot-pr-prompt-analysis.lock.yml | 2 +- .../copilot-session-insights.lock.yml | 2 +- .../workflows/copilot-token-audit.lock.yml | 2 +- .../copilot-token-optimizer.lock.yml | 2 +- .../workflows/daily-cli-performance.lock.yml | 2 +- .github/workflows/daily-code-metrics.lock.yml | 2 +- .../daily-community-attribution.lock.yml | 2 +- .github/workflows/daily-news.lock.yml | 2 +- .../daily-testify-uber-super-expert.lock.yml | 2 +- .github/workflows/deep-report.lock.yml | 2 +- .github/workflows/delight.lock.yml | 2 +- .../developer-docs-consolidator.lock.yml | 2 +- .../workflows/discussion-task-miner.lock.yml | 2 +- .github/workflows/firewall-escape.lock.yml | 2 +- .../workflows/glossary-maintainer.lock.yml | 2 +- .github/workflows/metrics-collector.lock.yml | 2 +- .github/workflows/pr-triage-agent.lock.yml | 2 +- .../workflows/security-compliance.lock.yml | 2 +- .../workflows/technical-doc-writer.lock.yml | 2 +- .../weekly-blog-post-writer.lock.yml | 2 +- .../workflow-health-manager.lock.yml | 2 +- pkg/workflow/repo_memory.go | 19 +++++--- pkg/workflow/repo_memory_test.go | 43 +++++++++++++++++++ 29 files changed, 82 insertions(+), 34 deletions(-) diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index 33a1e8cebfe..a5f9bd57e4e 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -1326,7 +1326,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index bd33da3ed19..8bdd8360225 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -1395,7 +1395,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/code-scanning-fixer.lock.yml b/.github/workflows/code-scanning-fixer.lock.yml index 0604235bffb..6419cb6438c 100644 --- a/.github/workflows/code-scanning-fixer.lock.yml +++ b/.github/workflows/code-scanning-fixer.lock.yml @@ -1264,7 +1264,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 0b4f40158f7..6c95fdce69c 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -1285,7 +1285,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/copilot-cli-deep-research.lock.yml b/.github/workflows/copilot-cli-deep-research.lock.yml index ec701b1024a..d21d091ef50 100644 --- a/.github/workflows/copilot-cli-deep-research.lock.yml +++ b/.github/workflows/copilot-cli-deep-research.lock.yml @@ -1179,7 +1179,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 250dcb91aed..28df58019ec 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -1289,7 +1289,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 1fe15240a8f..b74888cac1a 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -1210,7 +1210,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 571752dfc7d..03905198553 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -1362,7 +1362,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/copilot-token-audit.lock.yml b/.github/workflows/copilot-token-audit.lock.yml index fe2cc5e9ecf..6a1c9fc2d93 100644 --- a/.github/workflows/copilot-token-audit.lock.yml +++ b/.github/workflows/copilot-token-audit.lock.yml @@ -1366,7 +1366,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/copilot-token-optimizer.lock.yml b/.github/workflows/copilot-token-optimizer.lock.yml index 059ad97e152..a1040a574b0 100644 --- a/.github/workflows/copilot-token-optimizer.lock.yml +++ b/.github/workflows/copilot-token-optimizer.lock.yml @@ -1210,7 +1210,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index 885c8faa168..78bf9c5c706 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -1458,7 +1458,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index baff5bfa423..31eb8080048 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -1373,7 +1373,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/daily-community-attribution.lock.yml b/.github/workflows/daily-community-attribution.lock.yml index 3a1d982ce2c..9547e1c8add 100644 --- a/.github/workflows/daily-community-attribution.lock.yml +++ b/.github/workflows/daily-community-attribution.lock.yml @@ -1242,7 +1242,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index c2dd5c9280d..6c5d256698f 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -1404,7 +1404,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index c078ddb1012..f00218b135e 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -1346,7 +1346,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 8ff5a60ad29..2e489899436 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -1420,7 +1420,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index 65e196bacbb..6defd74c2ec 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -1225,7 +1225,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index b8415baef85..8c58af42163 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -1397,7 +1397,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/discussion-task-miner.lock.yml b/.github/workflows/discussion-task-miner.lock.yml index 997d5d6a71d..8444bd1f09f 100644 --- a/.github/workflows/discussion-task-miner.lock.yml +++ b/.github/workflows/discussion-task-miner.lock.yml @@ -1214,7 +1214,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/firewall-escape.lock.yml b/.github/workflows/firewall-escape.lock.yml index 34ca45f5be1..d133972f0ef 100644 --- a/.github/workflows/firewall-escape.lock.yml +++ b/.github/workflows/firewall-escape.lock.yml @@ -1291,7 +1291,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index eb0a327a7a6..6fc1beaf3ef 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -1291,7 +1291,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/metrics-collector.lock.yml b/.github/workflows/metrics-collector.lock.yml index f98ced1d78a..6f02d9f3ee5 100644 --- a/.github/workflows/metrics-collector.lock.yml +++ b/.github/workflows/metrics-collector.lock.yml @@ -719,7 +719,7 @@ jobs: needs: - activation - agent - if: always() && needs.agent.result == 'success' + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index 979bed171b8..bf4a40264f8 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -1206,7 +1206,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index f54ce86cd2f..92657734687 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -1174,7 +1174,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 80dfa6b15a2..c5d25c4a5a2 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -1271,7 +1271,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/weekly-blog-post-writer.lock.yml b/.github/workflows/weekly-blog-post-writer.lock.yml index 414b6443b7b..bc1f28c2532 100644 --- a/.github/workflows/weekly-blog-post-writer.lock.yml +++ b/.github/workflows/weekly-blog-post-writer.lock.yml @@ -1262,7 +1262,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 0f6913762be..512051e94d6 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -1278,7 +1278,7 @@ jobs: - detection if: > always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/pkg/workflow/repo_memory.go b/pkg/workflow/repo_memory.go index 170ddb08ef9..71deddb1dad 100644 --- a/pkg/workflow/repo_memory.go +++ b/pkg/workflow/repo_memory.go @@ -736,21 +736,26 @@ func (c *Compiler) buildPushRepoMemoryJob(data *WorkflowData, threatDetectionEna steps = append(steps, c.generateRestoreActionsSetupStep()) } - // Job condition: only run if the agent job succeeded (do not push repo memory when agent - // failed or was skipped). Using always() so the job still runs even when upstream jobs - // are skipped (e.g. detection is skipped when agent produces no outputs). - agentSucceeded := BuildEquals( + // Job condition: only run when the agent actually executed (not skipped). + // Using always() so the job still runs even when upstream jobs are skipped + // (e.g. detection is skipped when agent produces no outputs). + // We check != 'skipped' rather than == 'success' so that repo-memory is + // pushed even when the agent fails — partial memory data is still valuable. + // Crucially, this prevents the job from running on no-op workflow invocations + // (e.g. bot comments) where pre_activation is skipped and the skip cascades + // through activation → agent → detection. + agentRan := BuildNotEquals( BuildPropertyAccess(fmt.Sprintf("needs.%s.result", constants.AgentJobName)), - BuildStringLiteral("success"), + BuildStringLiteral("skipped"), ) jobNeeds := []string{string(constants.AgentJobName), string(constants.ActivationJobName)} var jobCondition string if threatDetectionEnabled { // When threat detection is enabled, also require detection passed (succeeded or skipped). - jobCondition = RenderCondition(BuildAnd(BuildAnd(BuildFunctionCall("always"), buildDetectionPassedCondition()), agentSucceeded)) + jobCondition = RenderCondition(BuildAnd(BuildAnd(BuildFunctionCall("always"), buildDetectionPassedCondition()), agentRan)) jobNeeds = append(jobNeeds, string(constants.DetectionJobName)) } else { - jobCondition = RenderCondition(BuildAnd(BuildFunctionCall("always"), agentSucceeded)) + jobCondition = RenderCondition(BuildAnd(BuildFunctionCall("always"), agentRan)) } // Build outputs map for validation failures from all memory steps diff --git a/pkg/workflow/repo_memory_test.go b/pkg/workflow/repo_memory_test.go index 578aa179ef4..4d6723f7260 100644 --- a/pkg/workflow/repo_memory_test.go +++ b/pkg/workflow/repo_memory_test.go @@ -1281,3 +1281,46 @@ func TestPushRepoMemoryJobConcurrencyKey(t *testing.T) { assert.NotContains(t, pushJob.Concurrency, "push-repo-memory-${{ github.repository }}\"", "Concurrency key should not be the old repo-wide-only key format") } + +// TestPushRepoMemoryJobConditionGatesOnAgentNotSkipped verifies that the push_repo_memory +// job condition uses needs.agent.result != 'skipped' so the job does not run on no-op +// workflow invocations (e.g. bot comments where pre_activation is skipped). +func TestPushRepoMemoryJobConditionGatesOnAgentNotSkipped(t *testing.T) { + data := &WorkflowData{ + RepoMemoryConfig: &RepoMemoryConfig{ + Memories: []RepoMemoryEntry{ + {ID: "default", BranchName: "memory/my-workflow"}, + }, + }, + } + + compiler := NewCompiler() + + t.Run("without threat detection", func(t *testing.T) { + pushJob, err := compiler.buildPushRepoMemoryJob(data, false) + require.NoError(t, err, "Should build push job without error") + require.NotNil(t, pushJob, "Should produce a push job") + + assert.Contains(t, pushJob.If, "always()", + "Condition should contain always() to bypass normal skip propagation") + assert.Contains(t, pushJob.If, "!= 'skipped'", + "Condition should check agent result != 'skipped' to gate on agent having run") + assert.NotContains(t, pushJob.If, "== 'success'", + "Condition should NOT use == 'success' — agent failures should still push memory") + }) + + t.Run("with threat detection", func(t *testing.T) { + pushJob, err := compiler.buildPushRepoMemoryJob(data, true) + require.NoError(t, err, "Should build push job without error") + require.NotNil(t, pushJob, "Should produce a push job") + + assert.Contains(t, pushJob.If, "always()", + "Condition should contain always()") + assert.Contains(t, pushJob.If, "!= 'skipped'", + "Condition should check agent result != 'skipped'") + assert.Contains(t, pushJob.If, "needs.detection.result", + "Condition should still check detection result when threat detection is enabled") + assert.NotContains(t, pushJob.If, "needs.agent.result == 'success'", + "Condition should NOT use == 'success' for agent check") + }) +} From 1638f4ec926bc786b1fa543fc2c62bdcb4c0bf30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 01:58:44 +0000 Subject: [PATCH 3/3] fix: add !cancelled() guard and rename agentRan to agentNotSkipped Address review feedback: 1. Add !cancelled() to prevent push_repo_memory from running after workflow cancellation (consistent with compiler_safe_outputs_job.go) 2. Rename agentRan to agentNotSkipped for consistency with the naming convention used elsewhere in the codebase 3. Update test to assert full expected condition string and verify !cancelled() presence Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b01603df-bac9-4013-8fe3-6f9d9e8a2d41 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../agent-performance-analyzer.lock.yml | 2 +- .github/workflows/audit-workflows.lock.yml | 2 +- .../workflows/code-scanning-fixer.lock.yml | 2 +- .../workflows/copilot-agent-analysis.lock.yml | 2 +- .../copilot-cli-deep-research.lock.yml | 2 +- .../copilot-pr-nlp-analysis.lock.yml | 2 +- .../copilot-pr-prompt-analysis.lock.yml | 2 +- .../copilot-session-insights.lock.yml | 2 +- .../workflows/copilot-token-audit.lock.yml | 2 +- .../copilot-token-optimizer.lock.yml | 2 +- .../workflows/daily-cli-performance.lock.yml | 2 +- .github/workflows/daily-code-metrics.lock.yml | 2 +- .../daily-community-attribution.lock.yml | 2 +- .github/workflows/daily-news.lock.yml | 2 +- .../daily-testify-uber-super-expert.lock.yml | 2 +- .github/workflows/deep-report.lock.yml | 2 +- .github/workflows/delight.lock.yml | 2 +- .../developer-docs-consolidator.lock.yml | 2 +- .../workflows/discussion-task-miner.lock.yml | 2 +- .github/workflows/firewall-escape.lock.yml | 2 +- .../workflows/glossary-maintainer.lock.yml | 2 +- .github/workflows/metrics-collector.lock.yml | 2 +- .github/workflows/pr-triage-agent.lock.yml | 2 +- .../workflows/security-compliance.lock.yml | 2 +- .../workflows/technical-doc-writer.lock.yml | 2 +- .../weekly-blog-post-writer.lock.yml | 2 +- .../workflow-health-manager.lock.yml | 2 +- pkg/workflow/repo_memory.go | 25 +++++++++++-------- pkg/workflow/repo_memory_test.go | 20 ++++++++------- 29 files changed, 52 insertions(+), 47 deletions(-) diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index a5f9bd57e4e..9185f2a0488 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -1325,7 +1325,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 8bdd8360225..81e91e498df 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -1394,7 +1394,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/code-scanning-fixer.lock.yml b/.github/workflows/code-scanning-fixer.lock.yml index 6419cb6438c..4391128b13f 100644 --- a/.github/workflows/code-scanning-fixer.lock.yml +++ b/.github/workflows/code-scanning-fixer.lock.yml @@ -1263,7 +1263,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 6c95fdce69c..7f152fe3678 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -1284,7 +1284,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/copilot-cli-deep-research.lock.yml b/.github/workflows/copilot-cli-deep-research.lock.yml index d21d091ef50..b32f0fd2b3e 100644 --- a/.github/workflows/copilot-cli-deep-research.lock.yml +++ b/.github/workflows/copilot-cli-deep-research.lock.yml @@ -1178,7 +1178,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 28df58019ec..9b1d531d03b 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -1288,7 +1288,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index b74888cac1a..24095673893 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -1209,7 +1209,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 03905198553..7229561fab6 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -1361,7 +1361,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/copilot-token-audit.lock.yml b/.github/workflows/copilot-token-audit.lock.yml index 6a1c9fc2d93..992c8b1c1ec 100644 --- a/.github/workflows/copilot-token-audit.lock.yml +++ b/.github/workflows/copilot-token-audit.lock.yml @@ -1365,7 +1365,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/copilot-token-optimizer.lock.yml b/.github/workflows/copilot-token-optimizer.lock.yml index a1040a574b0..16aadb96df9 100644 --- a/.github/workflows/copilot-token-optimizer.lock.yml +++ b/.github/workflows/copilot-token-optimizer.lock.yml @@ -1209,7 +1209,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index 78bf9c5c706..6d5e1551775 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -1457,7 +1457,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index 31eb8080048..81ef1649c08 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -1372,7 +1372,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/daily-community-attribution.lock.yml b/.github/workflows/daily-community-attribution.lock.yml index 9547e1c8add..001dd8be058 100644 --- a/.github/workflows/daily-community-attribution.lock.yml +++ b/.github/workflows/daily-community-attribution.lock.yml @@ -1241,7 +1241,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 6c5d256698f..e967497933e 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -1403,7 +1403,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index f00218b135e..bff81e6276d 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -1345,7 +1345,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 2e489899436..9a647df004d 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -1419,7 +1419,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index 6defd74c2ec..a4ef8fb6fd5 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -1224,7 +1224,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 8c58af42163..8ce1fad5e17 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -1396,7 +1396,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/discussion-task-miner.lock.yml b/.github/workflows/discussion-task-miner.lock.yml index 8444bd1f09f..6d95e6a484c 100644 --- a/.github/workflows/discussion-task-miner.lock.yml +++ b/.github/workflows/discussion-task-miner.lock.yml @@ -1213,7 +1213,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/firewall-escape.lock.yml b/.github/workflows/firewall-escape.lock.yml index d133972f0ef..7dfd06e2437 100644 --- a/.github/workflows/firewall-escape.lock.yml +++ b/.github/workflows/firewall-escape.lock.yml @@ -1290,7 +1290,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 6fc1beaf3ef..7096b56a03a 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -1290,7 +1290,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/metrics-collector.lock.yml b/.github/workflows/metrics-collector.lock.yml index 6f02d9f3ee5..bd04ccb0fb5 100644 --- a/.github/workflows/metrics-collector.lock.yml +++ b/.github/workflows/metrics-collector.lock.yml @@ -719,7 +719,7 @@ jobs: needs: - activation - agent - if: always() && needs.agent.result != 'skipped' + if: always() && (!cancelled()) && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index bf4a40264f8..ff55fb35c7a 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -1205,7 +1205,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index 92657734687..38592bbd2c2 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -1173,7 +1173,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index c5d25c4a5a2..f6bb4770f70 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -1270,7 +1270,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/weekly-blog-post-writer.lock.yml b/.github/workflows/weekly-blog-post-writer.lock.yml index bc1f28c2532..d344ea87e74 100644 --- a/.github/workflows/weekly-blog-post-writer.lock.yml +++ b/.github/workflows/weekly-blog-post-writer.lock.yml @@ -1261,7 +1261,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 512051e94d6..2890fb42e1b 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -1277,7 +1277,7 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: diff --git a/pkg/workflow/repo_memory.go b/pkg/workflow/repo_memory.go index 71deddb1dad..29008bb3899 100644 --- a/pkg/workflow/repo_memory.go +++ b/pkg/workflow/repo_memory.go @@ -736,26 +736,29 @@ func (c *Compiler) buildPushRepoMemoryJob(data *WorkflowData, threatDetectionEna steps = append(steps, c.generateRestoreActionsSetupStep()) } - // Job condition: only run when the agent actually executed (not skipped). - // Using always() so the job still runs even when upstream jobs are skipped - // (e.g. detection is skipped when agent produces no outputs). - // We check != 'skipped' rather than == 'success' so that repo-memory is - // pushed even when the agent fails — partial memory data is still valuable. - // Crucially, this prevents the job from running on no-op workflow invocations - // (e.g. bot comments) where pre_activation is skipped and the skip cascades - // through activation → agent → detection. - agentRan := BuildNotEquals( + // Job condition: only run when the agent actually executed (not skipped) and + // the workflow was not cancelled. Using always() so the job still runs even + // when upstream jobs are skipped (e.g. detection is skipped when agent produces + // no outputs). We check != 'skipped' rather than == 'success' so that + // repo-memory is pushed even when the agent fails — partial memory data is + // still valuable. Adding !cancelled() prevents the job from running after + // workflow cancellation (similar to compiler_safe_outputs_job.go). + // Crucially, the != 'skipped' check prevents the job from running on no-op + // workflow invocations (e.g. bot comments) where pre_activation is skipped + // and the skip cascades through activation → agent → detection. + agentNotSkipped := BuildNotEquals( BuildPropertyAccess(fmt.Sprintf("needs.%s.result", constants.AgentJobName)), BuildStringLiteral("skipped"), ) + notCancelled := &NotNode{Child: BuildFunctionCall("cancelled")} jobNeeds := []string{string(constants.AgentJobName), string(constants.ActivationJobName)} var jobCondition string if threatDetectionEnabled { // When threat detection is enabled, also require detection passed (succeeded or skipped). - jobCondition = RenderCondition(BuildAnd(BuildAnd(BuildFunctionCall("always"), buildDetectionPassedCondition()), agentRan)) + jobCondition = RenderCondition(BuildAnd(BuildAnd(BuildAnd(BuildFunctionCall("always"), notCancelled), buildDetectionPassedCondition()), agentNotSkipped)) jobNeeds = append(jobNeeds, string(constants.DetectionJobName)) } else { - jobCondition = RenderCondition(BuildAnd(BuildFunctionCall("always"), agentRan)) + jobCondition = RenderCondition(BuildAnd(BuildAnd(BuildFunctionCall("always"), notCancelled), agentNotSkipped)) } // Build outputs map for validation failures from all memory steps diff --git a/pkg/workflow/repo_memory_test.go b/pkg/workflow/repo_memory_test.go index 4d6723f7260..1ca8e2777e5 100644 --- a/pkg/workflow/repo_memory_test.go +++ b/pkg/workflow/repo_memory_test.go @@ -1283,8 +1283,9 @@ func TestPushRepoMemoryJobConcurrencyKey(t *testing.T) { } // TestPushRepoMemoryJobConditionGatesOnAgentNotSkipped verifies that the push_repo_memory -// job condition uses needs.agent.result != 'skipped' so the job does not run on no-op -// workflow invocations (e.g. bot comments where pre_activation is skipped). +// job condition uses needs.agent.result != 'skipped' and !cancelled() so the job does not +// run on no-op workflow invocations (e.g. bot comments where pre_activation is skipped) +// or after workflow cancellation. func TestPushRepoMemoryJobConditionGatesOnAgentNotSkipped(t *testing.T) { data := &WorkflowData{ RepoMemoryConfig: &RepoMemoryConfig{ @@ -1301,12 +1302,11 @@ func TestPushRepoMemoryJobConditionGatesOnAgentNotSkipped(t *testing.T) { require.NoError(t, err, "Should build push job without error") require.NotNil(t, pushJob, "Should produce a push job") - assert.Contains(t, pushJob.If, "always()", - "Condition should contain always() to bypass normal skip propagation") - assert.Contains(t, pushJob.If, "!= 'skipped'", - "Condition should check agent result != 'skipped' to gate on agent having run") - assert.NotContains(t, pushJob.If, "== 'success'", - "Condition should NOT use == 'success' — agent failures should still push memory") + assert.Equal(t, + "always() && (!cancelled()) && needs.agent.result != 'skipped'", + pushJob.If, + "Condition should use always() && (!cancelled()) && agent != skipped", + ) }) t.Run("with threat detection", func(t *testing.T) { @@ -1316,7 +1316,9 @@ func TestPushRepoMemoryJobConditionGatesOnAgentNotSkipped(t *testing.T) { assert.Contains(t, pushJob.If, "always()", "Condition should contain always()") - assert.Contains(t, pushJob.If, "!= 'skipped'", + assert.Contains(t, pushJob.If, "!cancelled()", + "Condition should contain !cancelled() to prevent running after cancellation") + assert.Contains(t, pushJob.If, "needs.agent.result != 'skipped'", "Condition should check agent result != 'skipped'") assert.Contains(t, pushJob.If, "needs.detection.result", "Condition should still check detection result when threat detection is enabled")