diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index 33a1e8cebfe..9185f2a0488 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -1325,8 +1325,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..81e91e498df 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -1394,8 +1394,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..4391128b13f 100644 --- a/.github/workflows/code-scanning-fixer.lock.yml +++ b/.github/workflows/code-scanning-fixer.lock.yml @@ -1263,8 +1263,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..7f152fe3678 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -1284,8 +1284,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..b32f0fd2b3e 100644 --- a/.github/workflows/copilot-cli-deep-research.lock.yml +++ b/.github/workflows/copilot-cli-deep-research.lock.yml @@ -1178,8 +1178,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..9b1d531d03b 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -1288,8 +1288,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..24095673893 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -1209,8 +1209,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..7229561fab6 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -1361,8 +1361,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..992c8b1c1ec 100644 --- a/.github/workflows/copilot-token-audit.lock.yml +++ b/.github/workflows/copilot-token-audit.lock.yml @@ -1365,8 +1365,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..16aadb96df9 100644 --- a/.github/workflows/copilot-token-optimizer.lock.yml +++ b/.github/workflows/copilot-token-optimizer.lock.yml @@ -1209,8 +1209,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..6d5e1551775 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -1457,8 +1457,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..81ef1649c08 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -1372,8 +1372,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..001dd8be058 100644 --- a/.github/workflows/daily-community-attribution.lock.yml +++ b/.github/workflows/daily-community-attribution.lock.yml @@ -1241,8 +1241,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..e967497933e 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -1403,8 +1403,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..bff81e6276d 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -1345,8 +1345,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..9a647df004d 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -1419,8 +1419,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..a4ef8fb6fd5 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -1224,8 +1224,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..8ce1fad5e17 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -1396,8 +1396,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..6d95e6a484c 100644 --- a/.github/workflows/discussion-task-miner.lock.yml +++ b/.github/workflows/discussion-task-miner.lock.yml @@ -1213,8 +1213,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..7dfd06e2437 100644 --- a/.github/workflows/firewall-escape.lock.yml +++ b/.github/workflows/firewall-escape.lock.yml @@ -1290,8 +1290,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..7096b56a03a 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -1290,8 +1290,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..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 == 'success' + 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 979bed171b8..ff55fb35c7a 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -1205,8 +1205,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..38592bbd2c2 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -1173,8 +1173,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..f6bb4770f70 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -1270,8 +1270,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..d344ea87e74 100644 --- a/.github/workflows/weekly-blog-post-writer.lock.yml +++ b/.github/workflows/weekly-blog-post-writer.lock.yml @@ -1261,8 +1261,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..2890fb42e1b 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -1277,8 +1277,8 @@ jobs: - agent - detection if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + 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..29008bb3899 100644 --- a/pkg/workflow/repo_memory.go +++ b/pkg/workflow/repo_memory.go @@ -736,21 +736,29 @@ 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) 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("success"), + 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()), agentSucceeded)) + jobCondition = RenderCondition(BuildAnd(BuildAnd(BuildAnd(BuildFunctionCall("always"), notCancelled), buildDetectionPassedCondition()), agentNotSkipped)) jobNeeds = append(jobNeeds, string(constants.DetectionJobName)) } else { - jobCondition = RenderCondition(BuildAnd(BuildFunctionCall("always"), agentSucceeded)) + 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 578aa179ef4..1ca8e2777e5 100644 --- a/pkg/workflow/repo_memory_test.go +++ b/pkg/workflow/repo_memory_test.go @@ -1281,3 +1281,48 @@ 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' 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{ + 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.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) { + 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, "!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") + assert.NotContains(t, pushJob.If, "needs.agent.result == 'success'", + "Condition should NOT use == 'success' for agent check") + }) +}