From 1fdb7969acc6932e3a03278b12306505c66da549 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:17:35 +0000 Subject: [PATCH 1/2] feat: log maintenance workflow trigger reason Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/maintenance_workflow.go | 27 +++++++++++----- pkg/workflow/maintenance_workflow_test.go | 39 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go index 7717b387662..4d62911a039 100644 --- a/pkg/workflow/maintenance_workflow.go +++ b/pkg/workflow/maintenance_workflow.go @@ -167,7 +167,7 @@ func GenerateMaintenanceWorkflow(ctx context.Context, opts GenerateMaintenanceWo runsOnValue := FormatRunsOn(configuredRunsOn, defaultRunsOn) // Scan workflows for expires fields and track the minimum expires value - hasExpires, minExpires := scanWorkflowsForExpires(workflowDataList) + hasExpires, minExpires, triggerReason := scanWorkflowsForExpires(workflowDataList) // Get the setup action reference (local or remote based on mode). // Use the first available WorkflowData's ActionResolver to enable SHA pinning. @@ -210,6 +210,7 @@ func GenerateMaintenanceWorkflow(ctx context.Context, opts GenerateMaintenanceWo }) } + maintenanceLog.Printf("Maintenance workflow generation triggered: %s", triggerReason) maintenanceLog.Printf("Generating maintenance workflow for expired discussions, issues, and pull requests (minimum expires: %d hours)", minExpires) // Convert hours to days for cron schedule generation @@ -338,10 +339,20 @@ func allCopilotWorkflowsUseOrgBilling(workflowDataList []*WorkflowData) bool { } // scanWorkflowsForExpires checks all workflow data for expires fields and returns -// whether any expires fields are set and the minimum expires value in hours. -func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int) { +// whether any expires fields are set, the minimum expires value in hours, and the +// first reason that triggered maintenance workflow generation. +func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int, string) { hasExpires := false minExpires := 0 // Track minimum expires value in hours + triggerReason := "" + + setTriggerReason := func(reason string) { + if !hasExpires { + hasExpires = true + triggerReason = reason + maintenanceLog.Printf("Maintenance workflow became required: %s", reason) + } + } for _, workflowData := range workflowDataList { if workflowData == nil || workflowData.SafeOutputs == nil { @@ -350,8 +361,8 @@ func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int) { // Check for expired discussions if workflowData.SafeOutputs.CreateDiscussions != nil { if workflowData.SafeOutputs.CreateDiscussions.Expires > 0 { - hasExpires = true expires := workflowData.SafeOutputs.CreateDiscussions.Expires + setTriggerReason(fmt.Sprintf("workflow %q sets safe_outputs.create_discussions.expires=%dh", workflowData.Name, expires)) maintenanceLog.Printf("Workflow %s has expires field set to %d hours for discussions", workflowData.Name, expires) if minExpires == 0 || expires < minExpires { minExpires = expires @@ -361,8 +372,8 @@ func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int) { // Check for expired issues if workflowData.SafeOutputs.CreateIssues != nil { if workflowData.SafeOutputs.CreateIssues.Expires > 0 { - hasExpires = true expires := workflowData.SafeOutputs.CreateIssues.Expires + setTriggerReason(fmt.Sprintf("workflow %q sets safe_outputs.create_issues.expires=%dh", workflowData.Name, expires)) maintenanceLog.Printf("Workflow %s has expires field set to %d hours for issues", workflowData.Name, expires) if minExpires == 0 || expires < minExpires { minExpires = expires @@ -372,8 +383,8 @@ func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int) { // Check for expired pull requests if workflowData.SafeOutputs.CreatePullRequests != nil { if workflowData.SafeOutputs.CreatePullRequests.Expires > 0 { - hasExpires = true expires := workflowData.SafeOutputs.CreatePullRequests.Expires + setTriggerReason(fmt.Sprintf("workflow %q sets safe_outputs.create_pull_requests.expires=%dh", workflowData.Name, expires)) maintenanceLog.Printf("Workflow %s has expires field set to %d hours for pull requests", workflowData.Name, expires) if minExpires == 0 || expires < minExpires { minExpires = expires @@ -383,8 +394,8 @@ func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int) { // Check for no-op runs issue expiration (runtime defaults to 30 days) if workflowData.SafeOutputs.NoOp != nil { if isNoOpReportAsIssueEnabled(workflowData.SafeOutputs.NoOp.ReportAsIssue) { - hasExpires = true expires := defaultNoOpIssueExpirationHours + setTriggerReason(fmt.Sprintf("workflow %q enables no-op issue reporting (default expiration %dh)", workflowData.Name, expires)) maintenanceLog.Printf("Workflow %s has no-op report-as-issue enabled, using %d-hour no-op issue expiration", workflowData.Name, expires) if minExpires == 0 || expires < minExpires { minExpires = expires @@ -393,5 +404,5 @@ func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int) { } } - return hasExpires, minExpires + return hasExpires, minExpires, triggerReason } diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index 36a44bc701e..7bdf5c31555 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -230,6 +230,45 @@ func TestGenerateMaintenanceWorkflow_WithExpires(t *testing.T) { } } +func TestScanWorkflowsForExpires_TriggerReason(t *testing.T) { + t.Run("no triggers", func(t *testing.T) { + hasExpires, minExpires, triggerReason := scanWorkflowsForExpires([]*WorkflowData{ + { + Name: "no-safe-outputs", + SafeOutputs: nil, + }, + }) + require.False(t, hasExpires) + require.Equal(t, 0, minExpires) + require.Empty(t, triggerReason) + }) + + t.Run("captures first trigger reason", func(t *testing.T) { + hasExpires, minExpires, triggerReason := scanWorkflowsForExpires([]*WorkflowData{ + { + Name: "first-trigger", + SafeOutputs: &SafeOutputsConfig{ + CreateIssues: &CreateIssuesConfig{ + Expires: 72, + }, + }, + }, + { + Name: "second-trigger", + SafeOutputs: &SafeOutputsConfig{ + CreateDiscussions: &CreateDiscussionsConfig{ + Expires: 24, + }, + }, + }, + }) + require.True(t, hasExpires) + require.Equal(t, 24, minExpires) + require.Contains(t, triggerReason, "first-trigger") + require.Contains(t, triggerReason, "safe_outputs.create_issues.expires=72h") + }) +} + func TestGenerateMaintenanceWorkflow_CreatesWorkflowDirRecursively(t *testing.T) { tmpDir := t.TempDir() workflowDir := filepath.Join(tmpDir, "nested", "workflows") From 0275acc65e9bfbc25bcb26e76f64836094d0eafa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:26:34 +0000 Subject: [PATCH 2/2] test: ensure maintenance trigger reason captures first source Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/maintenance_workflow.go | 7 +++++-- pkg/workflow/maintenance_workflow_test.go | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go index 4d62911a039..7adde7a22b2 100644 --- a/pkg/workflow/maintenance_workflow.go +++ b/pkg/workflow/maintenance_workflow.go @@ -347,8 +347,7 @@ func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int, strin triggerReason := "" setTriggerReason := func(reason string) { - if !hasExpires { - hasExpires = true + if triggerReason == "" { triggerReason = reason maintenanceLog.Printf("Maintenance workflow became required: %s", reason) } @@ -361,6 +360,7 @@ func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int, strin // Check for expired discussions if workflowData.SafeOutputs.CreateDiscussions != nil { if workflowData.SafeOutputs.CreateDiscussions.Expires > 0 { + hasExpires = true expires := workflowData.SafeOutputs.CreateDiscussions.Expires setTriggerReason(fmt.Sprintf("workflow %q sets safe_outputs.create_discussions.expires=%dh", workflowData.Name, expires)) maintenanceLog.Printf("Workflow %s has expires field set to %d hours for discussions", workflowData.Name, expires) @@ -372,6 +372,7 @@ func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int, strin // Check for expired issues if workflowData.SafeOutputs.CreateIssues != nil { if workflowData.SafeOutputs.CreateIssues.Expires > 0 { + hasExpires = true expires := workflowData.SafeOutputs.CreateIssues.Expires setTriggerReason(fmt.Sprintf("workflow %q sets safe_outputs.create_issues.expires=%dh", workflowData.Name, expires)) maintenanceLog.Printf("Workflow %s has expires field set to %d hours for issues", workflowData.Name, expires) @@ -383,6 +384,7 @@ func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int, strin // Check for expired pull requests if workflowData.SafeOutputs.CreatePullRequests != nil { if workflowData.SafeOutputs.CreatePullRequests.Expires > 0 { + hasExpires = true expires := workflowData.SafeOutputs.CreatePullRequests.Expires setTriggerReason(fmt.Sprintf("workflow %q sets safe_outputs.create_pull_requests.expires=%dh", workflowData.Name, expires)) maintenanceLog.Printf("Workflow %s has expires field set to %d hours for pull requests", workflowData.Name, expires) @@ -394,6 +396,7 @@ func scanWorkflowsForExpires(workflowDataList []*WorkflowData) (bool, int, strin // Check for no-op runs issue expiration (runtime defaults to 30 days) if workflowData.SafeOutputs.NoOp != nil { if isNoOpReportAsIssueEnabled(workflowData.SafeOutputs.NoOp.ReportAsIssue) { + hasExpires = true expires := defaultNoOpIssueExpirationHours setTriggerReason(fmt.Sprintf("workflow %q enables no-op issue reporting (default expiration %dh)", workflowData.Name, expires)) maintenanceLog.Printf("Workflow %s has no-op report-as-issue enabled, using %d-hour no-op issue expiration", workflowData.Name, expires) diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index 7bdf5c31555..05402e5b61e 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -266,6 +266,7 @@ func TestScanWorkflowsForExpires_TriggerReason(t *testing.T) { require.Equal(t, 24, minExpires) require.Contains(t, triggerReason, "first-trigger") require.Contains(t, triggerReason, "safe_outputs.create_issues.expires=72h") + require.NotContains(t, triggerReason, "second-trigger") }) }