From 4bb6884b257d8ac49e70cc57dc35389824587269 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Jul 2026 06:06:27 +0000 Subject: [PATCH 1/2] Initial plan From 9812f9213843c4e0e0348cc212274af19b98aa82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Jul 2026 06:28:12 +0000 Subject: [PATCH 2/2] Fix pr-sous-chef conclusion checkout failure cascade Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/pr-sous-chef.lock.yml | 35 ++++++++++++ pkg/workflow/compiler_custom_actions_test.go | 56 +++++++++++++++++++ pkg/workflow/compiler_yaml_step_generation.go | 49 ++++++++++++++++ .../notify_comment_conclusion_helpers.go | 10 +++- 4 files changed, 148 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-sous-chef.lock.yml b/.github/workflows/pr-sous-chef.lock.yml index 13679ca5fc5..eb224be5072 100644 --- a/.github/workflows/pr-sous-chef.lock.yml +++ b/.github/workflows/pr-sous-chef.lock.yml @@ -1491,6 +1491,22 @@ jobs: total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Checkout actions folder + id: checkout-actions-folder-attempt-1 + continue-on-error: true + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false + - name: Backoff before retrying checkout actions folder (5s) + if: steps.checkout-actions-folder-attempt-1.outcome == 'failure' + run: sleep 5 + - name: Checkout actions folder (retry 1) + id: checkout-actions-folder-attempt-2 + if: steps.checkout-actions-folder-attempt-1.outcome == 'failure' + continue-on-error: true uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: github/gh-aw @@ -1498,6 +1514,25 @@ jobs: actions clean: false persist-credentials: false + - name: Backoff before retrying checkout actions folder (15s) + if: steps.checkout-actions-folder-attempt-2.outcome == 'failure' + run: sleep 15 + - name: Checkout actions folder (retry 2) + id: checkout-actions-folder-attempt-3 + if: steps.checkout-actions-folder-attempt-2.outcome == 'failure' + continue-on-error: true + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false + - name: Fail with clear checkout error after retries + if: steps.checkout-actions-folder-attempt-3.outcome == 'failure' + run: | + echo "::error::Failed to checkout github/gh-aw actions folder after 3 attempts (5s/15s backoff). This is usually a transient GitHub DNS or network failure." + exit 1 - name: Setup Scripts id: setup uses: ./actions/setup diff --git a/pkg/workflow/compiler_custom_actions_test.go b/pkg/workflow/compiler_custom_actions_test.go index 0126b4d7386..795b083cbaf 100644 --- a/pkg/workflow/compiler_custom_actions_test.go +++ b/pkg/workflow/compiler_custom_actions_test.go @@ -396,6 +396,62 @@ func TestCheckoutActionsFolderDevModeAlwaysEmitsCheckout(t *testing.T) { } } +func TestCheckoutActionsFolderWithRetryDevMode(t *testing.T) { + compiler := NewCompiler(WithVersion("dev")) + compiler.SetActionMode(ActionModeDev) + + lines := compiler.generateCheckoutActionsFolderWithRetry(nil) + combined := strings.Join(lines, "") + + if !strings.Contains(combined, "id: checkout-actions-folder-attempt-1") { + t.Error("Expected first checkout attempt id in retry checkout step") + } + if !strings.Contains(combined, "id: checkout-actions-folder-attempt-2") { + t.Error("Expected second checkout attempt id in retry checkout step") + } + if !strings.Contains(combined, "id: checkout-actions-folder-attempt-3") { + t.Error("Expected third checkout attempt id in retry checkout step") + } + if !strings.Contains(combined, "Backoff before retrying checkout actions folder (5s)") { + t.Error("Expected 5s backoff step in retry checkout flow") + } + if !strings.Contains(combined, "Backoff before retrying checkout actions folder (15s)") { + t.Error("Expected 15s backoff step in retry checkout flow") + } + if !strings.Contains(combined, "Fail with clear checkout error after retries") { + t.Error("Expected explicit final checkout failure step") + } + if !strings.Contains(combined, "continue-on-error: true") { + t.Error("Expected checkout attempts to use continue-on-error: true") + } + if !strings.Contains(combined, "Failed to checkout github/gh-aw actions folder after 3 attempts") { + t.Error("Expected clear checkout failure error message after retries") + } +} + +func TestConclusionSetupUsesRetryingCheckoutOnlyForPrSousChef(t *testing.T) { + compiler := NewCompiler(WithVersion("dev")) + compiler.SetActionMode(ActionModeDev) + + sousChefSteps := strings.Join(compiler.buildConclusionSetupSteps(&WorkflowData{ + Name: "PR Sous Chef", + WorkflowID: "pr-sous-chef", + SafeOutputs: &SafeOutputsConfig{}, + }), "") + if !strings.Contains(sousChefSteps, "checkout-actions-folder-attempt-1") { + t.Error("Expected PR Sous Chef conclusion setup to include checkout retry steps") + } + + otherSteps := strings.Join(compiler.buildConclusionSetupSteps(&WorkflowData{ + Name: "Other Workflow", + WorkflowID: "other-workflow", + SafeOutputs: &SafeOutputsConfig{}, + }), "") + if strings.Contains(otherSteps, "checkout-actions-folder-attempt-1") { + t.Error("Did not expect non-PR-Sous-Chef conclusion setup to include checkout retry steps") + } +} + // TestResolveSetupActionReferenceActionMode tests that action mode resolves to the external gh-aw-actions repo func TestResolveSetupActionReferenceActionMode(t *testing.T) { ref := ResolveSetupActionReference(context.Background(), ActionModeAction, "v1.2.3", "", nil) diff --git a/pkg/workflow/compiler_yaml_step_generation.go b/pkg/workflow/compiler_yaml_step_generation.go index 475e824d6db..3b95c7b32a6 100644 --- a/pkg/workflow/compiler_yaml_step_generation.go +++ b/pkg/workflow/compiler_yaml_step_generation.go @@ -81,6 +81,55 @@ func (c *Compiler) generateCheckoutActionsFolder(data *WorkflowData) []string { return nil } +// generateCheckoutActionsFolderWithRetry generates checkout steps with retry/backoff. +// It is intended for failure-sensitive paths (for example conclusion jobs) where a +// transient checkout error should not cascade into misleading downstream failures. +func (c *Compiler) generateCheckoutActionsFolderWithRetry(data *WorkflowData) []string { + base := c.generateCheckoutActionsFolder(data) + if len(base) == 0 { + return nil + } + if len(base) < 2 { + return base + } + + baseBody := base[1:] // uses + with + options + buildAttempt := func(name, id, ifExpr string) []string { + lines := []string{ + fmt.Sprintf(" - name: %s\n", name), + fmt.Sprintf(" id: %s\n", id), + } + if ifExpr != "" { + lines = append(lines, fmt.Sprintf(" if: %s\n", ifExpr)) + } + lines = append(lines, " continue-on-error: true\n") + lines = append(lines, baseBody...) + return lines + } + + lines := buildAttempt("Checkout actions folder", "checkout-actions-folder-attempt-1", "") + lines = append(lines, + " - name: Backoff before retrying checkout actions folder (5s)\n", + " if: steps.checkout-actions-folder-attempt-1.outcome == 'failure'\n", + " run: sleep 5\n", + ) + lines = append(lines, buildAttempt("Checkout actions folder (retry 1)", "checkout-actions-folder-attempt-2", "steps.checkout-actions-folder-attempt-1.outcome == 'failure'")...) + lines = append(lines, + " - name: Backoff before retrying checkout actions folder (15s)\n", + " if: steps.checkout-actions-folder-attempt-2.outcome == 'failure'\n", + " run: sleep 15\n", + ) + lines = append(lines, buildAttempt("Checkout actions folder (retry 2)", "checkout-actions-folder-attempt-3", "steps.checkout-actions-folder-attempt-2.outcome == 'failure'")...) + lines = append(lines, + " - name: Fail with clear checkout error after retries\n", + " if: steps.checkout-actions-folder-attempt-3.outcome == 'failure'\n", + " run: |\n", + " echo \"::error::Failed to checkout github/gh-aw actions folder after 3 attempts (5s/15s backoff). This is usually a transient GitHub DNS or network failure.\"\n", + " exit 1\n", + ) + return lines +} + // generateRestoreActionsSetupStep generates a single "Restore actions folder" step that // re-checks out only the actions/setup subfolder from github/gh-aw. This is used in dev mode // after a job step has checked out a different repository (or a different git branch) and diff --git a/pkg/workflow/notify_comment_conclusion_helpers.go b/pkg/workflow/notify_comment_conclusion_helpers.go index e2401b69a78..1875027281a 100644 --- a/pkg/workflow/notify_comment_conclusion_helpers.go +++ b/pkg/workflow/notify_comment_conclusion_helpers.go @@ -22,8 +22,14 @@ func (c *Compiler) buildConclusionSetupSteps(data *WorkflowData) []string { // Add setup step to copy scripts setupActionRef := c.resolveActionReference("./actions/setup", data) if setupActionRef != "" || c.actionMode.IsScript() { - // For dev mode (local action path), checkout the actions folder first - steps = append(steps, c.generateCheckoutActionsFolder(data)...) + // For dev mode (local action path), checkout the actions folder first. + // PR Sous Chef conclusion has regressed on transient checkout failures: + // use retries here so they do not cascade into MODULE_NOT_FOUND errors. + if data != nil && data.WorkflowID == "pr-sous-chef" { + steps = append(steps, c.generateCheckoutActionsFolderWithRetry(data)...) + } else { + steps = append(steps, c.generateCheckoutActionsFolder(data)...) + } // Notify comment job doesn't need project support // Conclusion/notify job depends on activation, reuse its trace ID