From 2731c978caf5c775e751754301c766616ae1785d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 20:47:10 +0000 Subject: [PATCH 1/5] Initial plan From b4eb1f05f029309e5fac2fb35be97c98410f7f1a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 21:01:12 +0000 Subject: [PATCH 2/5] Add check_workflow_recompile_needed.cjs and update maintenance workflow - Created JavaScript script to check for outdated workflows and create issues - Updated maintenance_workflow.go to use the new JavaScript script - Added comprehensive tests for the new functionality - Script searches for existing issues to avoid duplicates - Creates issues with agentic instructions for GitHub Copilot Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 32 ++- .../js/check_workflow_recompile_needed.cjs | 185 +++++++++++++++++ .../check_workflow_recompile_needed.test.cjs | 188 ++++++++++++++++++ pkg/workflow/maintenance_workflow.go | 39 +++- 4 files changed, 422 insertions(+), 22 deletions(-) create mode 100644 actions/setup/js/check_workflow_recompile_needed.cjs create mode 100644 actions/setup/js/check_workflow_recompile_needed.test.cjs diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 89e6705a8dd..eaffd4eae7c 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -95,10 +95,18 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + issues: write steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Checkout actions folder + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + sparse-checkout: | + actions + persist-credentials: false + - name: Setup Go uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: @@ -113,17 +121,19 @@ jobs: ./gh-aw compile --validate --verbose echo "✓ All workflows compiled successfully" - - name: Check for out-of-sync workflows - run: | - if git diff --exit-code .github/workflows/*.lock.yml; then - echo "✓ All workflow lock files are up to date" - else - echo "::error::Some workflow lock files are out of sync. Run 'make recompile' locally." - echo "::group::Diff of out-of-sync files" - git diff .github/workflows/*.lock.yml - echo "::endgroup::" - exit 1 - fi + - name: Setup Scripts + uses: ./actions/setup + with: + destination: /tmp/gh-aw/actions + + - name: Check for out-of-sync workflows and create issue if needed + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const { setupGlobals } = require('/tmp/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/tmp/gh-aw/actions/check_workflow_recompile_needed.cjs'); + await main(); zizmor-scan: runs-on: ubuntu-latest diff --git a/actions/setup/js/check_workflow_recompile_needed.cjs b/actions/setup/js/check_workflow_recompile_needed.cjs new file mode 100644 index 00000000000..82bebac8f97 --- /dev/null +++ b/actions/setup/js/check_workflow_recompile_needed.cjs @@ -0,0 +1,185 @@ +// @ts-check +/// + +const { getErrorMessage } = require("./error_helpers.cjs"); + +/** + * Check if workflows need recompilation and create an issue if needed. + * This script: + * 1. Checks if there are out-of-sync workflow lock files + * 2. Searches for existing open issues about recompiling workflows + * 3. If workflows are out of sync and no issue exists, creates a new issue with agentic instructions + * + * @returns {Promise} + */ +async function main() { + const owner = context.repo.owner; + const repo = context.repo.repo; + + core.info("Checking for out-of-sync workflow lock files"); + + // Execute git diff to check for changes in lock files + let diffOutput = ""; + let hasChanges = false; + + try { + // Run git diff to check if there are any changes in lock files + await exec.exec("git", ["diff", "--exit-code", ".github/workflows/*.lock.yml"], { + ignoreReturnCode: true, + listeners: { + stdout: data => { + diffOutput += data.toString(); + }, + stderr: data => { + diffOutput += data.toString(); + }, + }, + }); + + // If git diff exits with code 0, there are no changes + // If it exits with code 1, there are changes + // We need to check if there's actual diff output + hasChanges = diffOutput.trim().length > 0; + } catch (error) { + core.error(`Failed to check for workflow changes: ${getErrorMessage(error)}`); + throw error; + } + + if (!hasChanges) { + core.info("✓ All workflow lock files are up to date"); + return; + } + + core.info("⚠ Detected out-of-sync workflow lock files"); + + // Capture the actual diff for the issue body + let detailedDiff = ""; + try { + await exec.exec("git", ["diff", ".github/workflows/*.lock.yml"], { + listeners: { + stdout: data => { + detailedDiff += data.toString(); + }, + }, + }); + } catch (error) { + core.warning(`Could not capture detailed diff: ${getErrorMessage(error)}`); + } + + // Search for existing open issue about workflow recompilation + const issueTitle = "Workflows need recompilation"; + const searchQuery = `repo:${owner}/${repo} is:issue is:open in:title "${issueTitle}"`; + + core.info(`Searching for existing issue with title: "${issueTitle}"`); + + try { + const searchResult = await github.rest.search.issuesAndPullRequests({ + q: searchQuery, + per_page: 1, + }); + + if (searchResult.data.total_count > 0) { + const existingIssue = searchResult.data.items[0]; + core.info(`Found existing issue #${existingIssue.number}: ${existingIssue.html_url}`); + core.info("Skipping issue creation (avoiding duplicate)"); + + // Add a comment to the existing issue with the new workflow run info + const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; + const runUrl = context.payload.repository + ? `${context.payload.repository.html_url}/actions/runs/${context.runId}` + : `${githubServer}/${owner}/${repo}/actions/runs/${context.runId}`; + const commentBody = `Workflows are still out of sync as of ${new Date().toISOString()}.\n\nSee [workflow run](${runUrl}) for details.`; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: existingIssue.number, + body: commentBody, + }); + + core.info(`✓ Added comment to existing issue #${existingIssue.number}`); + return; + } + } catch (error) { + core.error(`Failed to search for existing issues: ${getErrorMessage(error)}`); + throw error; + } + + // No existing issue found, create a new one + core.info("No existing issue found, creating a new issue with agentic instructions"); + + const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; + const runUrl = context.payload.repository + ? `${context.payload.repository.html_url}/actions/runs/${context.runId}` + : `${githubServer}/${owner}/${repo}/actions/runs/${context.runId}`; + + // Build the issue body with agentic instructions + const issueBody = `## Problem + +The workflow lock files (\`.lock.yml\`) are out of sync with their source markdown files (\`.md\`). This means the workflows that run in GitHub Actions are not using the latest configuration. + +## What needs to be done + +The workflows need to be recompiled to regenerate the lock files from the markdown sources. + +## Instructions for GitHub Copilot + +Please recompile all workflows by running the following command: + +\`\`\`bash +make recompile +\`\`\` + +This will: +1. Build the latest version of \`gh-aw\` +2. Compile all workflow markdown files to YAML lock files +3. Ensure all workflows are up to date + +After recompiling, commit the changes with a message like: +\`\`\` +Recompile workflows to update lock files +\`\`\` + +## Detected Changes + +The following workflow lock files have changes: + +
+View diff + +\`\`\`diff +${detailedDiff.substring(0, 50000)}${detailedDiff.length > 50000 ? "\n\n... (diff truncated)" : ""} +\`\`\` + +
+ +## References + +- **Failed Check:** [Workflow Run](${runUrl}) +- **Repository:** ${owner}/${repo} + +--- + +> This issue was automatically created by the agentics maintenance workflow. +`; + + try { + const newIssue = await github.rest.issues.create({ + owner, + repo, + title: issueTitle, + body: issueBody, + labels: ["maintenance", "workflows"], + }); + + core.info(`✓ Created issue #${newIssue.data.number}: ${newIssue.data.html_url}`); + + // Write to job summary + await core.summary.addHeading("Workflow Recompilation Needed", 2).addRaw(`Created issue [#${newIssue.data.number}](${newIssue.data.html_url}) to track workflow recompilation.`).write(); + } catch (error) { + core.error(`Failed to create issue: ${getErrorMessage(error)}`); + throw error; + } +} + +module.exports = { main }; diff --git a/actions/setup/js/check_workflow_recompile_needed.test.cjs b/actions/setup/js/check_workflow_recompile_needed.test.cjs new file mode 100644 index 00000000000..c97ab8391a7 --- /dev/null +++ b/actions/setup/js/check_workflow_recompile_needed.test.cjs @@ -0,0 +1,188 @@ +// @ts-check +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; + +describe("check_workflow_recompile_needed", () => { + let mockCore; + let mockGithub; + let mockContext; + let mockExec; + let originalGlobals; + + beforeEach(() => { + // Save original globals + originalGlobals = { + core: global.core, + github: global.github, + context: global.context, + exec: global.exec, + }; + + // Setup mock core module + mockCore = { + info: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + summary: { + addHeading: vi.fn().mockReturnThis(), + addRaw: vi.fn().mockReturnThis(), + write: vi.fn().mockResolvedValue(undefined), + }, + }; + + // Setup mock github module + mockGithub = { + rest: { + search: { + issuesAndPullRequests: vi.fn(), + }, + issues: { + create: vi.fn(), + createComment: vi.fn(), + }, + }, + }; + + // Setup mock context + mockContext = { + repo: { + owner: "testowner", + repo: "testrepo", + }, + runId: 123456, + payload: { + repository: { + html_url: "https://github.com/testowner/testrepo", + }, + }, + }; + + // Setup mock exec module + mockExec = { + exec: vi.fn(), + }; + + // Set globals for the module + global.core = mockCore; + global.github = mockGithub; + global.context = mockContext; + global.exec = mockExec; + }); + + afterEach(() => { + // Restore original globals + global.core = originalGlobals.core; + global.github = originalGlobals.github; + global.context = originalGlobals.context; + global.exec = originalGlobals.exec; + }); + + it("should report no changes when workflows are up to date", async () => { + // Mock exec to return no changes (empty diff output) + mockExec.exec.mockResolvedValue(0); + + const { main } = await import("./check_workflow_recompile_needed.cjs"); + await main(); + + expect(mockCore.info).toHaveBeenCalledWith("✓ All workflow lock files are up to date"); + expect(mockGithub.rest.search.issuesAndPullRequests).not.toHaveBeenCalled(); + }); + + it("should add comment to existing issue when workflows are out of sync", async () => { + // Mock exec to return changes (non-empty diff output) + mockExec.exec + .mockImplementationOnce(async (cmd, args, options) => { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("diff content")); + } + return 1; // Non-zero exit code indicates changes + }) + .mockImplementationOnce(async (cmd, args, options) => { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("detailed diff content")); + } + return 0; + }); + + // Mock search to return existing issue + mockGithub.rest.search.issuesAndPullRequests.mockResolvedValue({ + data: { + total_count: 1, + items: [ + { + number: 42, + html_url: "https://github.com/testowner/testrepo/issues/42", + }, + ], + }, + }); + + mockGithub.rest.issues.createComment.mockResolvedValue({}); + + const { main } = await import("./check_workflow_recompile_needed.cjs"); + await main(); + + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Found existing issue")); + expect(mockGithub.rest.issues.createComment).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + issue_number: 42, + body: expect.stringContaining("Workflows are still out of sync"), + }); + expect(mockGithub.rest.issues.create).not.toHaveBeenCalled(); + }); + + it("should create new issue when workflows are out of sync and no issue exists", async () => { + // Mock exec to return changes (non-empty diff output) + mockExec.exec + .mockImplementationOnce(async (cmd, args, options) => { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("diff content")); + } + return 1; + }) + .mockImplementationOnce(async (cmd, args, options) => { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("detailed diff content")); + } + return 0; + }); + + // Mock search to return no existing issue + mockGithub.rest.search.issuesAndPullRequests.mockResolvedValue({ + data: { + total_count: 0, + items: [], + }, + }); + + mockGithub.rest.issues.create.mockResolvedValue({ + data: { + number: 43, + html_url: "https://github.com/testowner/testrepo/issues/43", + }, + }); + + const { main } = await import("./check_workflow_recompile_needed.cjs"); + await main(); + + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("No existing issue found")); + expect(mockGithub.rest.issues.create).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + title: "Workflows need recompilation", + body: expect.stringContaining("Instructions for GitHub Copilot"), + labels: ["maintenance", "workflows"], + }); + }); + + it("should handle errors gracefully", async () => { + // Mock exec to throw error + mockExec.exec.mockRejectedValue(new Error("Git command failed")); + + const { main } = await import("./check_workflow_recompile_needed.cjs"); + + await expect(main()).rejects.toThrow("Git command failed"); + expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("Failed to check for workflow changes")); + }); +}); + diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go index b08304896b8..d874a03d8d8 100644 --- a/pkg/workflow/maintenance_workflow.go +++ b/pkg/workflow/maintenance_workflow.go @@ -204,10 +204,25 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + issues: write steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 +`) + + // Add checkout for actions folder only in dev mode + if actionMode == ActionModeDev { + yaml.WriteString(` + - name: Checkout actions folder + uses: ` + GetActionPin("actions/checkout") + ` + with: + sparse-checkout: | + actions + persist-credentials: false +`) + } + yaml.WriteString(` - name: Setup Go uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: @@ -222,17 +237,19 @@ jobs: ./gh-aw compile --validate --verbose echo "✓ All workflows compiled successfully" - - name: Check for out-of-sync workflows - run: | - if git diff --exit-code .github/workflows/*.lock.yml; then - echo "✓ All workflow lock files are up to date" - else - echo "::error::Some workflow lock files are out of sync. Run 'make recompile' locally." - echo "::group::Diff of out-of-sync files" - git diff .github/workflows/*.lock.yml - echo "::endgroup::" - exit 1 - fi + - name: Setup Scripts + uses: ` + setupActionRef + ` + with: + destination: /tmp/gh-aw/actions + + - name: Check for out-of-sync workflows and create issue if needed + uses: ` + GetActionPin("actions/github-script") + ` + with: + script: | + const { setupGlobals } = require('/tmp/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/tmp/gh-aw/actions/check_workflow_recompile_needed.cjs'); + await main(); zizmor-scan: runs-on: ubuntu-latest From 3d4d9422c0625a783bca6d38e032fce2b1146656 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 21:07:14 +0000 Subject: [PATCH 3/5] Format JavaScript files with prettier Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/check_workflow_recompile_needed.cjs | 8 ++------ actions/setup/js/check_workflow_recompile_needed.test.cjs | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/actions/setup/js/check_workflow_recompile_needed.cjs b/actions/setup/js/check_workflow_recompile_needed.cjs index 82bebac8f97..678d389c8cd 100644 --- a/actions/setup/js/check_workflow_recompile_needed.cjs +++ b/actions/setup/js/check_workflow_recompile_needed.cjs @@ -85,9 +85,7 @@ async function main() { // Add a comment to the existing issue with the new workflow run info const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; - const runUrl = context.payload.repository - ? `${context.payload.repository.html_url}/actions/runs/${context.runId}` - : `${githubServer}/${owner}/${repo}/actions/runs/${context.runId}`; + const runUrl = context.payload.repository ? `${context.payload.repository.html_url}/actions/runs/${context.runId}` : `${githubServer}/${owner}/${repo}/actions/runs/${context.runId}`; const commentBody = `Workflows are still out of sync as of ${new Date().toISOString()}.\n\nSee [workflow run](${runUrl}) for details.`; await github.rest.issues.createComment({ @@ -109,9 +107,7 @@ async function main() { core.info("No existing issue found, creating a new issue with agentic instructions"); const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; - const runUrl = context.payload.repository - ? `${context.payload.repository.html_url}/actions/runs/${context.runId}` - : `${githubServer}/${owner}/${repo}/actions/runs/${context.runId}`; + const runUrl = context.payload.repository ? `${context.payload.repository.html_url}/actions/runs/${context.runId}` : `${githubServer}/${owner}/${repo}/actions/runs/${context.runId}`; // Build the issue body with agentic instructions const issueBody = `## Problem diff --git a/actions/setup/js/check_workflow_recompile_needed.test.cjs b/actions/setup/js/check_workflow_recompile_needed.test.cjs index c97ab8391a7..24ac927b544 100644 --- a/actions/setup/js/check_workflow_recompile_needed.test.cjs +++ b/actions/setup/js/check_workflow_recompile_needed.test.cjs @@ -185,4 +185,3 @@ describe("check_workflow_recompile_needed", () => { expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("Failed to check for workflow changes")); }); }); - From 2192c9e9aa332da5709359dd4942920bce15f269 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 22:21:24 +0000 Subject: [PATCH 4/5] Use sparse checkout of .github folder with persist-credentials: false Updated the compile-workflows job checkout step to use sparse-checkout for .github folder instead of full repository checkout, and added persist-credentials: false for security. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 4 ++++ pkg/workflow/maintenance_workflow.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index eaffd4eae7c..03a63b1c574 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -99,6 +99,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + sparse-checkout: | + .github + persist-credentials: false - name: Checkout actions folder uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go index d874a03d8d8..efbec489146 100644 --- a/pkg/workflow/maintenance_workflow.go +++ b/pkg/workflow/maintenance_workflow.go @@ -208,6 +208,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + sparse-checkout: | + .github + persist-credentials: false `) // Add checkout for actions folder only in dev mode From abd6e65f63ce7afb1b7e214891de536e69e0cb67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 22:37:45 +0000 Subject: [PATCH 5/5] Update checkout strategy: full repo in dev mode, sparse .github in release mode In dev mode, checkout the entire repository (needed for building). In release mode, use sparse checkout of only .github folder. Both modes use persist-credentials: false for security. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 8 -------- pkg/workflow/maintenance_workflow.go | 23 ++++++++++++---------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 03a63b1c574..3c03dae2e97 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -100,16 +100,8 @@ jobs: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - sparse-checkout: | - .github persist-credentials: false - - name: Checkout actions folder - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - sparse-checkout: | - actions - persist-credentials: false - name: Setup Go uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go index efbec489146..2b90b9358fa 100644 --- a/pkg/workflow/maintenance_workflow.go +++ b/pkg/workflow/maintenance_workflow.go @@ -206,23 +206,26 @@ jobs: contents: read issues: write steps: - - name: Checkout repository +`) + + // Checkout step - different behavior based on mode + if actionMode == ActionModeDev { + // Dev mode: checkout entire repository (no sparse checkout, but no credentials) + yaml.WriteString(` - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - sparse-checkout: | - .github persist-credentials: false -`) - // Add checkout for actions folder only in dev mode - if actionMode == ActionModeDev { - yaml.WriteString(` - - name: Checkout actions folder - uses: ` + GetActionPin("actions/checkout") + ` +`) + } else { + // Release mode: sparse checkout of .github folder only + yaml.WriteString(` - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: sparse-checkout: | - actions + .github persist-credentials: false + `) }