From 15ff2fef23530f738b5b16db012778b3ff2a3189 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 19:00:00 +0000 Subject: [PATCH 1/4] Initial plan From 5246e3bdf58081170a510b0a353670c880e4add2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 19:19:43 +0000 Subject: [PATCH 2/4] Add support for checkout: false to disable agent job checkout Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 7 +- pkg/workflow/checkout_disabled_test.go | 102 ++++++++++++++++++ pkg/workflow/compiler_jobs.go | 7 ++ .../compiler_orchestrator_workflow.go | 5 +- pkg/workflow/compiler_types.go | 1 + pkg/workflow/frontmatter_types.go | 21 ++-- pkg/workflow/frontmatter_types_test.go | 59 ++++++++++ 7 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 pkg/workflow/checkout_disabled_test.go diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 7c812bea099..3cf53dfe21a 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -7665,7 +7665,7 @@ "additionalProperties": false }, "checkout": { - "description": "Checkout configuration for the agent job. Controls how actions/checkout is invoked. Can be a single checkout configuration or an array for multiple checkouts.", + "description": "Checkout configuration for the agent job. Controls how actions/checkout is invoked. Can be a single checkout configuration, an array for multiple checkouts, or false to disable the default checkout step entirely (dev-mode checkouts are unaffected).", "oneOf": [ { "$ref": "#/$defs/checkoutConfig", @@ -7677,6 +7677,11 @@ "items": { "$ref": "#/$defs/checkoutConfig" } + }, + { + "type": "boolean", + "enum": [false], + "description": "Set to false to disable the default checkout step. The agent job will not check out any repository (dev-mode checkouts are unaffected)." } ] }, diff --git a/pkg/workflow/checkout_disabled_test.go b/pkg/workflow/checkout_disabled_test.go new file mode 100644 index 00000000000..f5f3e807f97 --- /dev/null +++ b/pkg/workflow/checkout_disabled_test.go @@ -0,0 +1,102 @@ +//go:build integration + +package workflow + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/github/gh-aw/pkg/stringutil" + "github.com/github/gh-aw/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCheckoutDisabled(t *testing.T) { + tests := []struct { + name string + frontmatter string + expectedHasDefaultCheckout bool + expectedHasDevModeCheckout bool + description string + }{ + { + name: "checkout: false disables agent job default checkout", + frontmatter: `--- +on: + issues: + types: [opened] +permissions: + contents: read + issues: read + pull-requests: read +tools: + github: + toolsets: [issues] +engine: claude +checkout: false +strict: false +---`, + expectedHasDefaultCheckout: false, + expectedHasDevModeCheckout: true, + description: "checkout: false should disable the default repository checkout step but leave dev-mode checkouts intact", + }, + { + name: "checkout absent still adds checkout by default", + frontmatter: `--- +on: + issues: + types: [opened] +permissions: + contents: read + issues: read + pull-requests: read +tools: + github: + toolsets: [issues] +engine: claude +strict: false +---`, + expectedHasDefaultCheckout: true, + expectedHasDevModeCheckout: true, + description: "When checkout is not set, the default checkout step is included", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := testutil.TempDir(t, "checkout-disabled-test") + + testContent := tt.frontmatter + "\n\n# Test Workflow\n\nThis is a test workflow.\n" + testFile := filepath.Join(tmpDir, "test-workflow.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644), "should write test file") + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(testFile), "should compile workflow") + + lockFile := stringutil.MarkdownToLockFile(testFile) + lockContent, err := os.ReadFile(lockFile) + require.NoError(t, err, "should read lock file") + + lockContentStr := string(lockContent) + + // Find the agent job section + agentJobStart := strings.Index(lockContentStr, "\n agent:") + require.NotEqual(t, -1, agentJobStart, "agent job not found in compiled workflow") + + // Extract from agent job to end + agentSection := lockContentStr[agentJobStart:] + + // The default workspace checkout is identified by "name: Checkout repository" + hasDefaultCheckout := strings.Contains(agentSection, "name: Checkout repository") + + // Dev-mode checkouts (e.g. "Checkout actions folder") should always be present + hasDevModeCheckout := strings.Contains(agentSection, "name: Checkout actions folder") + + assert.Equal(t, tt.expectedHasDefaultCheckout, hasDefaultCheckout, "%s: default checkout presence mismatch", tt.description) + assert.Equal(t, tt.expectedHasDevModeCheckout, hasDevModeCheckout, "%s: dev-mode checkout should not be affected", tt.description) + }) + } +} diff --git a/pkg/workflow/compiler_jobs.go b/pkg/workflow/compiler_jobs.go index b371d5d6fc6..9756e09c833 100644 --- a/pkg/workflow/compiler_jobs.go +++ b/pkg/workflow/compiler_jobs.go @@ -746,9 +746,16 @@ func (c *Compiler) buildCustomJobs(data *WorkflowData, activationJobCreated bool // // The checkout step is only skipped when: // - Custom steps already contain a checkout action +// - checkout: false is set in the workflow frontmatter // // Otherwise, checkout is always added to ensure the agent has access to the repository. func (c *Compiler) shouldAddCheckoutStep(data *WorkflowData) bool { + // If checkout was explicitly disabled via checkout: false, skip it + if data.CheckoutDisabled { + log.Print("Skipping checkout step: checkout disabled via checkout: false") + return false + } + // If custom steps already contain checkout, don't add another one if data.CustomSteps != "" && ContainsCheckout(data.CustomSteps) { log.Print("Skipping checkout step: custom steps already contain checkout") diff --git a/pkg/workflow/compiler_orchestrator_workflow.go b/pkg/workflow/compiler_orchestrator_workflow.go index 1da057ff624..f68a68f2b88 100644 --- a/pkg/workflow/compiler_orchestrator_workflow.go +++ b/pkg/workflow/compiler_orchestrator_workflow.go @@ -201,8 +201,11 @@ func (c *Compiler) buildInitialWorkflowData( // (e.g. due to unrecognised tool config shapes like bash: ["*"]). if toolsResult.parsedFrontmatter != nil { workflowData.CheckoutConfigs = toolsResult.parsedFrontmatter.CheckoutConfigs + workflowData.CheckoutDisabled = toolsResult.parsedFrontmatter.CheckoutDisabled } else if rawCheckout, ok := result.Frontmatter["checkout"]; ok { - if configs, err := ParseCheckoutConfigs(rawCheckout); err == nil { + if checkoutValue, ok := rawCheckout.(bool); ok && !checkoutValue { + workflowData.CheckoutDisabled = true + } else if configs, err := ParseCheckoutConfigs(rawCheckout); err == nil { workflowData.CheckoutConfigs = configs } } diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index 45367b4d31e..6f7ce097dbc 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -419,6 +419,7 @@ type WorkflowData struct { HasExplicitGitHubTool bool // true if tools.github was explicitly configured in frontmatter InlinedImports bool // if true, inline all imports at compile time (from inlined-imports frontmatter field) CheckoutConfigs []*CheckoutConfig // user-configured checkout settings from frontmatter + CheckoutDisabled bool // true when checkout: false is set in frontmatter HasDispatchItemNumber bool // true when workflow_dispatch has item_number input (generated by label trigger shorthand) ConcurrencyJobDiscriminator string // optional discriminator expression appended to job-level concurrency groups (from concurrency.job-discriminator) IsDetectionRun bool // true when this WorkflowData is used for inline threat detection (not the main agent run) diff --git a/pkg/workflow/frontmatter_types.go b/pkg/workflow/frontmatter_types.go index 64ba420d7da..0395514511b 100644 --- a/pkg/workflow/frontmatter_types.go +++ b/pkg/workflow/frontmatter_types.go @@ -177,8 +177,10 @@ type FrontmatterConfig struct { // Checkout configuration for the agent job. // Controls how actions/checkout is invoked. // Can be a single CheckoutConfig object or an array of CheckoutConfig objects. - Checkout any `json:"checkout,omitempty"` // Raw value (object or array) - CheckoutConfigs []*CheckoutConfig `json:"-"` // Parsed checkout configs (not in JSON) + // Set to false to disable the default checkout step entirely. + Checkout any `json:"checkout,omitempty"` // Raw value (object, array, or false) + CheckoutConfigs []*CheckoutConfig `json:"-"` // Parsed checkout configs (not in JSON) + CheckoutDisabled bool `json:"-"` // true when checkout: false is set in frontmatter } // ParseFrontmatterConfig creates a FrontmatterConfig from a raw frontmatter map @@ -231,12 +233,17 @@ func ParseFrontmatterConfig(frontmatter map[string]any) (*FrontmatterConfig, err } } - // Parse checkout field - supports single object or array of objects + // Parse checkout field - supports single object, array of objects, or false to disable if config.Checkout != nil { - checkoutConfigs, err := ParseCheckoutConfigs(config.Checkout) - if err == nil { - config.CheckoutConfigs = checkoutConfigs - frontmatterTypesLog.Printf("Parsed checkout config: %d entries", len(checkoutConfigs)) + if checkoutValue, ok := config.Checkout.(bool); ok && !checkoutValue { + config.CheckoutDisabled = true + frontmatterTypesLog.Print("Checkout disabled via checkout: false") + } else { + checkoutConfigs, err := ParseCheckoutConfigs(config.Checkout) + if err == nil { + config.CheckoutConfigs = checkoutConfigs + frontmatterTypesLog.Printf("Parsed checkout config: %d entries", len(checkoutConfigs)) + } } } diff --git a/pkg/workflow/frontmatter_types_test.go b/pkg/workflow/frontmatter_types_test.go index 74ee4ae4ef0..afffbedada8 100644 --- a/pkg/workflow/frontmatter_types_test.go +++ b/pkg/workflow/frontmatter_types_test.go @@ -1390,3 +1390,62 @@ func TestRuntimesConfigToMapWithIfCondition(t *testing.T) { t.Error("node should not have if condition in map") } } + +func TestParseFrontmatterConfigCheckoutDisabled(t *testing.T) { + t.Run("checkout: false sets CheckoutDisabled", func(t *testing.T) { + frontmatter := map[string]any{ + "name": "test-workflow", + "engine": "claude", + "checkout": false, + } + + config, err := ParseFrontmatterConfig(frontmatter) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !config.CheckoutDisabled { + t.Error("CheckoutDisabled should be true when checkout: false is set") + } + if len(config.CheckoutConfigs) != 0 { + t.Errorf("CheckoutConfigs should be empty when checkout: false is set, got %d entries", len(config.CheckoutConfigs)) + } + }) + + t.Run("checkout: true is not a valid value (treated as object)", func(t *testing.T) { + frontmatter := map[string]any{ + "name": "test-workflow", + "engine": "claude", + "checkout": true, + } + + config, err := ParseFrontmatterConfig(frontmatter) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // checkout: true is not recognized as a disable flag; CheckoutDisabled stays false + if config.CheckoutDisabled { + t.Error("CheckoutDisabled should be false when checkout: true is set") + } + }) + + t.Run("checkout object leaves CheckoutDisabled false", func(t *testing.T) { + frontmatter := map[string]any{ + "name": "test-workflow", + "engine": "claude", + "checkout": map[string]any{ + "fetch-depth": 0, + }, + } + + config, err := ParseFrontmatterConfig(frontmatter) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if config.CheckoutDisabled { + t.Error("CheckoutDisabled should be false when checkout is an object") + } + if len(config.CheckoutConfigs) == 0 { + t.Error("CheckoutConfigs should be populated when checkout is an object") + } + }) +} From 203e7c33cad1ffedae342c7499debce996851748 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 20:09:07 +0000 Subject: [PATCH 3/4] Set checkout: false on ai-moderator workflow Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/ai-moderator.lock.yml | 12 +----------- .github/workflows/ai-moderator.md | 1 + 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ai-moderator.lock.yml b/.github/workflows/ai-moderator.lock.yml index 3ffba3039db..a67ec4762ca 100644 --- a/.github/workflows/ai-moderator.lock.yml +++ b/.github/workflows/ai-moderator.lock.yml @@ -22,7 +22,7 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"7039c1f6c974b0231340e2aff8c6a986faee8cb863dd865ba29d0c951c387ca8","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"84dc6484402345ed9710ec3cab04ff2100d41aedab7f617b2184e9afce576731","strict":true} name: "AI Moderator" "on": @@ -165,7 +165,6 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} run: | bash /opt/gh-aw/actions/create_prompt_first.sh { @@ -210,9 +209,6 @@ jobs: GH_AW_PROMPT_EOF - if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then - cat "/opt/gh-aw/prompts/pr_context_prompt.md" - fi cat << 'GH_AW_PROMPT_EOF' GH_AW_PROMPT_EOF @@ -248,7 +244,6 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | @@ -273,7 +268,6 @@ jobs: GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_IS_PR_COMMENT: process.env.GH_AW_IS_PR_COMMENT, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED } }); @@ -330,10 +324,6 @@ jobs: uses: ./actions/setup with: destination: /opt/gh-aw/actions - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - name: Create gh-aw temp directory run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh # Cache memory file share configuration from frontmatter processed below diff --git a/.github/workflows/ai-moderator.md b/.github/workflows/ai-moderator.md index a2137e74ccd..426cae48960 100644 --- a/.github/workflows/ai-moderator.md +++ b/.github/workflows/ai-moderator.md @@ -41,6 +41,7 @@ safe-outputs: max: 5 allowed-reasons: [spam] threat-detection: false +checkout: false --- # AI Moderator From d21035cc121eeaa380f565c4bf03cc0aa9ecb1b2 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Fri, 13 Mar 2026 13:35:31 -0700 Subject: [PATCH 4/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/workflow/checkout_disabled_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/workflow/checkout_disabled_test.go b/pkg/workflow/checkout_disabled_test.go index f5f3e807f97..9acf5ecbfbc 100644 --- a/pkg/workflow/checkout_disabled_test.go +++ b/pkg/workflow/checkout_disabled_test.go @@ -74,6 +74,7 @@ strict: false require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644), "should write test file") compiler := NewCompiler() + compiler.SetActionMode(ActionModeDev) require.NoError(t, compiler.CompileWorkflow(testFile), "should compile workflow") lockFile := stringutil.MarkdownToLockFile(testFile)