From 2c0df67dda41a9712f28f523614ab0795dfd36c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 00:57:01 +0000 Subject: [PATCH 1/4] Initial plan From fd2e002c1eb1edebbff3de4cdbfb02c7304a67c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 01:20:05 +0000 Subject: [PATCH 2/4] fix: add persist-credentials: false to checkout step to resolve artipacked findings Add persist-credentials: false to the Checkout code step in copilot-setup-steps.yml and the fallback default in yaml_import.go. This prevents credential persistence in .git/config, resolving the artipacked Medium-severity finding in copilot-token-audit and copilot-token-optimizer workflows. Fixes #25894 Agent-Logs-Url: https://github.com/github/gh-aw/sessions/8532fe30-792d-4e17-b1fe-53d8f52696db Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/copilot-setup-steps.yml | 2 ++ .github/workflows/copilot-token-audit.lock.yml | 2 ++ .github/workflows/copilot-token-optimizer.lock.yml | 2 ++ pkg/parser/yaml_import.go | 3 +++ 4 files changed, 9 insertions(+) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 2de1875bb32..2c7fd072eb8 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -17,6 +17,8 @@ jobs: run: curl -fsSL https://raw.githubusercontent.com/github/gh-aw/refs/heads/main/install-gh-aw.sh | bash - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Node.js uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f with: diff --git a/.github/workflows/copilot-token-audit.lock.yml b/.github/workflows/copilot-token-audit.lock.yml index 7268e4dbf53..26519c7a1e3 100644 --- a/.github/workflows/copilot-token-audit.lock.yml +++ b/.github/workflows/copilot-token-audit.lock.yml @@ -366,6 +366,8 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: diff --git a/.github/workflows/copilot-token-optimizer.lock.yml b/.github/workflows/copilot-token-optimizer.lock.yml index d7429f693a7..a7b675f2b1d 100644 --- a/.github/workflows/copilot-token-optimizer.lock.yml +++ b/.github/workflows/copilot-token-optimizer.lock.yml @@ -349,6 +349,8 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: diff --git a/pkg/parser/yaml_import.go b/pkg/parser/yaml_import.go index 7f1d7af347b..d848a29daf0 100644 --- a/pkg/parser/yaml_import.go +++ b/pkg/parser/yaml_import.go @@ -260,6 +260,9 @@ func ensureCheckoutStepFirst(steps []any) []any { defaultCheckoutStep := map[string]any{ "name": "Checkout code", "uses": "actions/checkout@v6", + "with": map[string]any{ + "persist-credentials": false, + }, } steps = append([]any{defaultCheckoutStep}, steps...) return steps From 8013be7b4a576fdb874ea97205350d85af23e2c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 01:41:21 +0000 Subject: [PATCH 3/4] refactor: strip checkout from imported copilot-setup-steps instead of duplicating Replace ensureCheckoutStepFirst with stripCheckoutSteps in yaml_import.go. The compiler generates its own secure checkout (with persist-credentials: false) via CheckoutManager.GenerateDefaultCheckoutStep, so the imported checkout from copilot-setup-steps.yml is redundant. Stripping it removes the duplicate "Checkout code" step from compiled lock files, resolving the artipacked finding without persisting a redundant checkout. Agent-Logs-Url: https://github.com/github/gh-aw/sessions/ae7ecb21-b2c1-4ffa-9e8c-c8bd5c4a3320 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../workflows/copilot-token-audit.lock.yml | 48 +++++++++-- .../copilot-token-optimizer.lock.yml | 14 ++-- pkg/parser/yaml_import.go | 61 +++++--------- pkg/parser/yaml_import_copilot_setup_test.go | 83 +++++++++---------- 4 files changed, 104 insertions(+), 102 deletions(-) diff --git a/.github/workflows/copilot-token-audit.lock.yml b/.github/workflows/copilot-token-audit.lock.yml index 26519c7a1e3..6fd7deaa66c 100644 --- a/.github/workflows/copilot-token-audit.lock.yml +++ b/.github/workflows/copilot-token-audit.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"30107ff06ddff13a593f175c86fe6ac713e80c7174980a1b05dddccb48fff938","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"668228422ae6a00e4ad889ee87cd7109ec5666a7","version":"v5.0.4"},{"repo":"actions/cache/save","sha":"668228422ae6a00e4ad889ee87cd7109ec5666a7","version":"v5.0.4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"53b83947a5a98c8d113130e565377fae1a50d02f","version":"v6.3.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7"},{"repo":"astral-sh/setup-uv","sha":"eac588ad8def6316056a12d4907a9d4d84ff7a3b","version":"eac588ad8def6316056a12d4907a9d4d84ff7a3b"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.18","digest":"sha256:c77e8c26bab6c39e8568d8e2f8c17015944849a8cbcdfb4bd9725d8893725ca2","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.18@sha256:c77e8c26bab6c39e8568d8e2f8c17015944849a8cbcdfb4bd9725d8893725ca2"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18","digest":"sha256:d16a40a3ca6e989896d0cef9f31b9412bb1fcc8755bafcafb95012ae1078539b","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18@sha256:d16a40a3ca6e989896d0cef9f31b9412bb1fcc8755bafcafb95012ae1078539b"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.18","digest":"sha256:eb102afcfbae26ffcec016adebb74d3be7b0a5bf376ba306599cdf3effbe288e","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.18@sha256:eb102afcfbae26ffcec016adebb74d3be7b0a5bf376ba306599cdf3effbe288e"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.17","digest":"sha256:a6dec6ec535a11c565d982afa2f98589805ed0598862b9ea9d3c751fc71afae8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.2.17@sha256:a6dec6ec535a11c565d982afa2f98589805ed0598862b9ea9d3c751fc71afae8"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0","digest":"sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28","pinned_image":"ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28"},{"image":"node:lts-alpine","digest":"sha256:01743339035a5c3c11a373cd7c83aeab6ed1457b55da6a69e014a95ac4e4700b","pinned_image":"node:lts-alpine@sha256:01743339035a5c3c11a373cd7c83aeab6ed1457b55da6a69e014a95ac4e4700b"}]} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"668228422ae6a00e4ad889ee87cd7109ec5666a7","version":"v5.0.4"},{"repo":"actions/cache/save","sha":"668228422ae6a00e4ad889ee87cd7109ec5666a7","version":"v5.0.4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"53b83947a5a98c8d113130e565377fae1a50d02f","version":"v6.3.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7"},{"repo":"astral-sh/setup-uv","sha":"eac588ad8def6316056a12d4907a9d4d84ff7a3b","version":"eac588ad8def6316056a12d4907a9d4d84ff7a3b"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.18","digest":"sha256:c77e8c26bab6c39e8568d8e2f8c17015944849a8cbcdfb4bd9725d8893725ca2","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.18@sha256:c77e8c26bab6c39e8568d8e2f8c17015944849a8cbcdfb4bd9725d8893725ca2"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18","digest":"sha256:d16a40a3ca6e989896d0cef9f31b9412bb1fcc8755bafcafb95012ae1078539b","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18@sha256:d16a40a3ca6e989896d0cef9f31b9412bb1fcc8755bafcafb95012ae1078539b"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.18","digest":"sha256:eb102afcfbae26ffcec016adebb74d3be7b0a5bf376ba306599cdf3effbe288e","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.18@sha256:eb102afcfbae26ffcec016adebb74d3be7b0a5bf376ba306599cdf3effbe288e"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.17","digest":"sha256:a6dec6ec535a11c565d982afa2f98589805ed0598862b9ea9d3c751fc71afae8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.2.17@sha256:a6dec6ec535a11c565d982afa2f98589805ed0598862b9ea9d3c751fc71afae8"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0","digest":"sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28","pinned_image":"ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28"},{"image":"node:lts-alpine","digest":"sha256:01743339035a5c3c11a373cd7c83aeab6ed1457b55da6a69e014a95ac4e4700b","pinned_image":"node:lts-alpine@sha256:01743339035a5c3c11a373cd7c83aeab6ed1457b55da6a69e014a95ac4e4700b"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -50,6 +50,8 @@ # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # - astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # eac588ad8def6316056a12d4907a9d4d84ff7a3b +# - docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 +# - docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.25.18@sha256:c77e8c26bab6c39e8568d8e2f8c17015944849a8cbcdfb4bd9725d8893725ca2 @@ -358,16 +360,40 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - - name: Create gh-aw temp directory - run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - - name: Configure gh CLI for GitHub Enterprise - run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" - env: - GH_TOKEN: ${{ github.token }} - - name: Checkout code + - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + - name: Setup Go for CLI build + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + cache: true + - name: Build gh-aw CLI + run: | + echo "Building gh-aw CLI for linux/amd64..." + mkdir -p dist + VERSION=$(git describe --tags --always --dirty) + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -ldflags "-s -w -X main.version=${VERSION}" \ + -o dist/gh-aw-linux-amd64 \ + ./cmd/gh-aw + # Copy binary to root for direct execution in user-defined steps + cp dist/gh-aw-linux-amd64 ./gh-aw + chmod +x ./gh-aw + echo "✓ Built gh-aw CLI successfully" + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 + - name: Build gh-aw Docker image + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + context: . + platforms: linux/amd64 + push: false + load: true + tags: localhost/gh-aw:dev + build-args: | + BINARY=dist/gh-aw-linux-amd64 - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: @@ -386,6 +412,12 @@ jobs: uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} - name: Install gh-aw extension run: curl -fsSL https://raw.githubusercontent.com/github/gh-aw/refs/heads/main/install-gh-aw.sh | bash - name: Install npm dependencies diff --git a/.github/workflows/copilot-token-optimizer.lock.yml b/.github/workflows/copilot-token-optimizer.lock.yml index a7b675f2b1d..c9b27c76ba5 100644 --- a/.github/workflows/copilot-token-optimizer.lock.yml +++ b/.github/workflows/copilot-token-optimizer.lock.yml @@ -341,13 +341,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - - name: Create gh-aw temp directory - run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - - name: Configure gh CLI for GitHub Enterprise - run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" - env: - GH_TOKEN: ${{ github.token }} - - name: Checkout code + - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -365,6 +359,12 @@ jobs: cache: 'npm' cache-dependency-path: 'actions/setup/js/package-lock.json' package-manager-cache: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} - name: Install gh-aw extension run: curl -fsSL https://raw.githubusercontent.com/github/gh-aw/refs/heads/main/install-gh-aw.sh | bash - name: Install npm dependencies diff --git a/pkg/parser/yaml_import.go b/pkg/parser/yaml_import.go index d848a29daf0..1223170ff71 100644 --- a/pkg/parser/yaml_import.go +++ b/pkg/parser/yaml_import.go @@ -205,8 +205,12 @@ func extractStepsFromCopilotSetup(workflow map[string]any) (string, error) { return "", errors.New("steps field is not a list in copilot-setup-steps job") } - // Ensure checkout step is always included and placed first - stepsSlice = ensureCheckoutStepFirst(stepsSlice) + // Strip checkout steps from the imported copilot-setup-steps. The compiler + // generates its own secure checkout (with persist-credentials: false) via + // CheckoutManager.GenerateDefaultCheckoutStep, so the imported checkout is + // redundant and can introduce artipacked findings when the same job uploads + // artifacts. + stepsSlice = stripCheckoutSteps(stepsSlice) // Marshal steps array directly to YAML format (without "steps:" wrapper) // This matches the format expected by the compiler which unmarshals into []any @@ -215,55 +219,30 @@ func extractStepsFromCopilotSetup(workflow map[string]any) (string, error) { return "", fmt.Errorf("failed to marshal steps to YAML: %w", err) } - yamlImportLog.Printf("Extracted steps from copilot-setup-steps job (YAML array format) with checkout step ensured") + yamlImportLog.Printf("Extracted steps from copilot-setup-steps job (YAML array format) with checkout steps stripped") return string(stepsYAML), nil } -// ensureCheckoutStepFirst ensures a checkout step exists and is placed first in the steps list -// If a checkout step exists, it's moved to the beginning. If not, one is added. -func ensureCheckoutStepFirst(steps []any) []any { - // Find existing checkout step index - checkoutIndex := -1 - for i, step := range steps { +// stripCheckoutSteps removes any actions/checkout steps from the imported +// copilot-setup-steps. The compiler generates its own secure checkout step +// (with persist-credentials: false), so the imported checkout is redundant. +// Stripping it prevents the artipacked finding where checkout + artifact +// upload coexist with persisted credentials, and avoids a duplicate checkout +// in the compiled lock file. +func stripCheckoutSteps(steps []any) []any { + result := make([]any, 0, len(steps)) + for _, step := range steps { if stepMap, ok := step.(map[string]any); ok { if uses, hasUses := stepMap["uses"]; hasUses { if usesStr, ok := uses.(string); ok { - // Check if this is a checkout action (actions/checkout@... or exactly "actions/checkout") if strings.HasPrefix(usesStr, "actions/checkout@") || usesStr == "actions/checkout" { - checkoutIndex = i - break + yamlImportLog.Printf("Stripping checkout step from copilot-setup-steps: %s", usesStr) + continue } } } } + result = append(result, step) } - - // If checkout step exists and is already first, no changes needed - if checkoutIndex == 0 { - yamlImportLog.Print("Checkout step already at beginning of copilot-setup-steps") - return steps - } - - // If checkout step exists but not first, move it to the beginning - if checkoutIndex > 0 { - yamlImportLog.Printf("Moving existing checkout step from position %d to beginning", checkoutIndex) - checkoutStep := steps[checkoutIndex] - // Remove from current position - steps = append(steps[:checkoutIndex], steps[checkoutIndex+1:]...) - // Prepend to beginning - steps = append([]any{checkoutStep}, steps...) - return steps - } - - // No checkout step found, add a default one at the beginning - yamlImportLog.Print("No checkout step found in copilot-setup-steps, adding default checkout step at beginning") - defaultCheckoutStep := map[string]any{ - "name": "Checkout code", - "uses": "actions/checkout@v6", - "with": map[string]any{ - "persist-credentials": false, - }, - } - steps = append([]any{defaultCheckoutStep}, steps...) - return steps + return result } diff --git a/pkg/parser/yaml_import_copilot_setup_test.go b/pkg/parser/yaml_import_copilot_setup_test.go index d88e2c98811..c962c3ea962 100644 --- a/pkg/parser/yaml_import_copilot_setup_test.go +++ b/pkg/parser/yaml_import_copilot_setup_test.go @@ -92,9 +92,9 @@ func TestExtractStepsFromCopilotSetup(t *testing.T) { // Verify the YAML contains the expected content (as a YAML array, not with "steps:" wrapper) assert.NotContains(t, stepsYAML, "steps:", "Should NOT contain steps field wrapper") assert.Contains(t, stepsYAML, "Install gh-aw extension", "Should contain install step") - assert.Contains(t, stepsYAML, "Checkout code", "Should contain checkout step") + assert.NotContains(t, stepsYAML, "Checkout code", "Should NOT contain checkout step (stripped by compiler)") + assert.NotContains(t, stepsYAML, "actions/checkout@v4", "Should NOT contain checkout action (stripped by compiler)") assert.Contains(t, stepsYAML, "Set up Node.js", "Should contain Node.js setup step") - assert.Contains(t, stepsYAML, "actions/checkout@v4", "Should contain checkout action") assert.Contains(t, stepsYAML, "actions/setup-node@v4", "Should contain Node.js setup action") // Verify it's formatted as a YAML array (starts with "- name:") @@ -143,8 +143,8 @@ func TestExtractStepsFromCopilotSetup_NoSteps(t *testing.T) { assert.Contains(t, err.Error(), "no steps found", "Error should mention missing steps") } -func TestExtractStepsFromCopilotSetup_EnsuresCheckoutFirst(t *testing.T) { - // Test workflow with checkout step NOT first +func TestExtractStepsFromCopilotSetup_StripsCheckoutStep(t *testing.T) { + // Test workflow with checkout step NOT first — checkout should be stripped workflow := map[string]any{ "name": "Copilot Setup Steps", "on": "workflow_dispatch", @@ -176,7 +176,11 @@ func TestExtractStepsFromCopilotSetup_EnsuresCheckoutFirst(t *testing.T) { require.NoError(t, err, "Should extract steps without error") require.NotEmpty(t, stepsYAML, "Should return non-empty steps YAML") - // Verify checkout step is first + // Verify checkout step is stripped (compiler handles checkout securely) + assert.NotContains(t, stepsYAML, "Checkout code", "Should NOT contain checkout step") + assert.NotContains(t, stepsYAML, "actions/checkout", "Should NOT contain checkout action") + + // Verify first step is now the install step (checkout was removed) lines := strings.Split(stepsYAML, "\n") var firstStepName string for _, line := range lines { @@ -185,23 +189,16 @@ func TestExtractStepsFromCopilotSetup_EnsuresCheckoutFirst(t *testing.T) { break } } - assert.Contains(t, firstStepName, "Checkout code", "First step should be the checkout step") + assert.Contains(t, firstStepName, "Install gh-aw extension", "First step should be the install step after checkout is stripped") - // Verify all steps are present + // Verify non-checkout steps are preserved assert.Contains(t, stepsYAML, "Install gh-aw extension", "Should contain install step") - assert.Contains(t, stepsYAML, "Checkout code", "Should contain checkout step") assert.Contains(t, stepsYAML, "Set up Node.js", "Should contain Node.js setup step") - - // Verify checkout comes before install (order should be: checkout, install, node) - checkoutIndex := strings.Index(stepsYAML, "Checkout code") - installIndex := strings.Index(stepsYAML, "Install gh-aw extension") - nodeIndex := strings.Index(stepsYAML, "Set up Node.js") - assert.Less(t, checkoutIndex, installIndex, "Checkout should come before install") - assert.Less(t, installIndex, nodeIndex, "Install should come before Node.js setup") } -func TestExtractStepsFromCopilotSetup_AddsCheckoutIfMissing(t *testing.T) { - // Test workflow without any checkout step +func TestExtractStepsFromCopilotSetup_NoCheckoutStaysClean(t *testing.T) { + // Test workflow without any checkout step — should remain without checkout + // since the compiler handles checkout generation securely workflow := map[string]any{ "name": "Copilot Setup Steps", "on": "workflow_dispatch", @@ -226,11 +223,15 @@ func TestExtractStepsFromCopilotSetup_AddsCheckoutIfMissing(t *testing.T) { require.NoError(t, err, "Should extract steps without error") require.NotEmpty(t, stepsYAML, "Should return non-empty steps YAML") - // Verify checkout step was added - assert.Contains(t, stepsYAML, "Checkout code", "Should contain added checkout step") - assert.Contains(t, stepsYAML, "actions/checkout@v6", "Should contain checkout action") + // Verify no checkout step was added (compiler handles it) + assert.NotContains(t, stepsYAML, "Checkout code", "Should NOT contain a checkout step") + assert.NotContains(t, stepsYAML, "actions/checkout", "Should NOT contain checkout action") + + // Verify original steps are still present + assert.Contains(t, stepsYAML, "Install dependencies", "Should contain original install step") + assert.Contains(t, stepsYAML, "Run linter", "Should contain original linter step") - // Verify checkout step is first + // Verify first step is Install dependencies lines := strings.Split(stepsYAML, "\n") var firstStepName string for _, line := range lines { @@ -239,22 +240,11 @@ func TestExtractStepsFromCopilotSetup_AddsCheckoutIfMissing(t *testing.T) { break } } - assert.Contains(t, firstStepName, "Checkout code", "First step should be the checkout step") - - // Verify original steps are still present - assert.Contains(t, stepsYAML, "Install dependencies", "Should contain original install step") - assert.Contains(t, stepsYAML, "Run linter", "Should contain original linter step") - - // Verify checkout comes before other steps - checkoutIndex := strings.Index(stepsYAML, "Checkout code") - installIndex := strings.Index(stepsYAML, "Install dependencies") - lintIndex := strings.Index(stepsYAML, "Run linter") - assert.Less(t, checkoutIndex, installIndex, "Checkout should come before install") - assert.Less(t, checkoutIndex, lintIndex, "Checkout should come before lint") + assert.Contains(t, firstStepName, "Install dependencies", "First step should be the install step") } -func TestExtractStepsFromCopilotSetup_CheckoutAlreadyFirst(t *testing.T) { - // Test workflow with checkout step already first +func TestExtractStepsFromCopilotSetup_CheckoutFirstIsStripped(t *testing.T) { + // Test workflow with checkout step already first — it should still be stripped workflow := map[string]any{ "name": "Copilot Setup Steps", "on": "workflow_dispatch", @@ -283,7 +273,15 @@ func TestExtractStepsFromCopilotSetup_CheckoutAlreadyFirst(t *testing.T) { require.NoError(t, err, "Should extract steps without error") require.NotEmpty(t, stepsYAML, "Should return non-empty steps YAML") - // Verify checkout step is first + // Verify checkout step is stripped + assert.NotContains(t, stepsYAML, "Checkout code", "Should NOT contain checkout step") + assert.NotContains(t, stepsYAML, "actions/checkout", "Should NOT contain checkout action") + + // Verify non-checkout steps are present + assert.Contains(t, stepsYAML, "Install dependencies", "Should contain install step") + assert.Contains(t, stepsYAML, "Run tests", "Should contain test step") + + // Verify first step is now Install dependencies lines := strings.Split(stepsYAML, "\n") var firstStepName string for _, line := range lines { @@ -292,18 +290,11 @@ func TestExtractStepsFromCopilotSetup_CheckoutAlreadyFirst(t *testing.T) { break } } - assert.Contains(t, firstStepName, "Checkout code", "First step should be the checkout step") - - // Verify all steps are present - assert.Contains(t, stepsYAML, "Checkout code", "Should contain checkout step") - assert.Contains(t, stepsYAML, "Install dependencies", "Should contain install step") - assert.Contains(t, stepsYAML, "Run tests", "Should contain test step") + assert.Contains(t, firstStepName, "Install dependencies", "First step should be install after checkout is stripped") - // Verify order is maintained - checkoutIndex := strings.Index(stepsYAML, "Checkout code") + // Verify order of remaining steps installIndex := strings.Index(stepsYAML, "Install dependencies") testIndex := strings.Index(stepsYAML, "Run tests") - assert.Less(t, checkoutIndex, installIndex, "Checkout should come before install") assert.Less(t, installIndex, testIndex, "Install should come before tests") } @@ -353,7 +344,7 @@ jobs: // Verify the steps YAML contains expected content (as a YAML array) assert.NotContains(t, stepsYAML, "steps:", "Should NOT contain steps field wrapper") assert.Contains(t, stepsYAML, "Install gh-aw extension", "Should contain install step") - assert.Contains(t, stepsYAML, "Checkout code", "Should contain checkout step") + assert.NotContains(t, stepsYAML, "Checkout code", "Should NOT contain checkout step (stripped)") assert.Contains(t, stepsYAML, "Set up Node.js", "Should contain Node.js setup step") assert.Contains(t, stepsYAML, "Set up Go", "Should contain Go setup step") From d8492a16aa432027c05f7a72af44f937d52c2074 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 01:45:06 +0000 Subject: [PATCH 4/4] fix: improve test assertion messages for accuracy Agent-Logs-Url: https://github.com/github/gh-aw/sessions/ae7ecb21-b2c1-4ffa-9e8c-c8bd5c4a3320 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/yaml_import_copilot_setup_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/parser/yaml_import_copilot_setup_test.go b/pkg/parser/yaml_import_copilot_setup_test.go index c962c3ea962..09e3de88d4a 100644 --- a/pkg/parser/yaml_import_copilot_setup_test.go +++ b/pkg/parser/yaml_import_copilot_setup_test.go @@ -92,8 +92,8 @@ func TestExtractStepsFromCopilotSetup(t *testing.T) { // Verify the YAML contains the expected content (as a YAML array, not with "steps:" wrapper) assert.NotContains(t, stepsYAML, "steps:", "Should NOT contain steps field wrapper") assert.Contains(t, stepsYAML, "Install gh-aw extension", "Should contain install step") - assert.NotContains(t, stepsYAML, "Checkout code", "Should NOT contain checkout step (stripped by compiler)") - assert.NotContains(t, stepsYAML, "actions/checkout@v4", "Should NOT contain checkout action (stripped by compiler)") + assert.NotContains(t, stepsYAML, "Checkout code", "Should NOT contain checkout step (stripped during import)") + assert.NotContains(t, stepsYAML, "actions/checkout@v4", "Should NOT contain checkout action (stripped during import)") assert.Contains(t, stepsYAML, "Set up Node.js", "Should contain Node.js setup step") assert.Contains(t, stepsYAML, "actions/setup-node@v4", "Should contain Node.js setup action")