From 0cd616254fe9110a2bdd3e90b9a03b4a82baff26 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 01:24:28 +0000 Subject: [PATCH 1/3] Initial plan From 20b1887abe91184a7dc598c5191ff388b7d5d0f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 01:28:44 +0000 Subject: [PATCH 2/3] Initial plan: Fix GH_TOKEN missing from make install step Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- .github/workflows/ci-coach.lock.yml | 180 ++++++++++++++++++++-------- 1 file changed, 129 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml index f5e3ca05785..e23e2391b8e 100644 --- a/.github/workflows/ci-coach.lock.yml +++ b/.github/workflows/ci-coach.lock.yml @@ -2102,7 +2102,7 @@ jobs: const { getCurrentBranch } = require("./get_current_branch.cjs"); const { getBaseBranch } = require("./get_base_branch.cjs"); const { generateGitPatch } = require("./generate_git_patch.cjs"); - function createHandlers(server, appendSafeOutput) { + function createHandlers(server, appendSafeOutput, config = {}) { const defaultHandler = type => args => { const entry = { ...(args || {}), type }; let largeContent = null; @@ -2224,6 +2224,23 @@ jobs: } entry.branch = detectedBranch; } + const allowEmpty = config.create_pull_request?.allow_empty === true; + if (allowEmpty) { + server.debug(`allow-empty is enabled for create_pull_request - skipping patch generation`); + appendSafeOutput(entry); + return { + content: [ + { + type: "text", + text: JSON.stringify({ + result: "success", + message: "Pull request prepared (allow-empty mode - no patch generated)", + branch: entry.branch, + }), + }, + ], + }; + } server.debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); const patchResult = generateGitPatch(entry.branch); if (!patchResult.success) { @@ -2307,7 +2324,7 @@ jobs: const server = createServer(SERVER_INFO, { logDir: MCP_LOG_DIR }); const { config: safeOutputsConfig, outputFile, tools: ALL_TOOLS } = bootstrapSafeOutputsServer(server); const appendSafeOutput = createAppendFunction(outputFile); - const handlers = createHandlers(server, appendSafeOutput); + const handlers = createHandlers(server, appendSafeOutput, safeOutputsConfig); const { defaultHandler } = handlers; const toolsWithHandlers = attachHandlers(ALL_TOOLS, handlers); server.debug(` output file: ${outputFile}`); @@ -4786,7 +4803,22 @@ jobs: const patchPath = "/tmp/gh-aw/aw.patch"; const hasPatch = fs.existsSync(patchPath); core.info(`Patch file ${hasPatch ? "exists" : "does not exist"} at: ${patchPath}`); - core.setOutput("has_patch", hasPatch ? "true" : "false"); + let allowEmptyPR = false; + if (safeOutputsConfig) { + if ( + safeOutputsConfig["create-pull-request"]?.["allow-empty"] === true || + safeOutputsConfig["create_pull_request"]?.["allow_empty"] === true + ) { + allowEmptyPR = true; + core.info(`allow-empty is enabled for create-pull-request`); + } + } + if (allowEmptyPR && !hasPatch && outputTypes.includes("create_pull_request")) { + core.info(`allow-empty is enabled and no patch exists - will create empty PR`); + core.setOutput("has_patch", "true"); + } else { + core.setOutput("has_patch", hasPatch ? "true" : "false"); + } } await main(); - name: Upload sanitized agent output @@ -7279,6 +7311,7 @@ jobs: GH_AW_PR_TITLE_PREFIX: "[ci-coach] " GH_AW_PR_DRAFT: "true" GH_AW_PR_IF_NO_CHANGES: "warn" + GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 GH_AW_WORKFLOW_NAME: "CI Optimization Coach" GH_AW_TRACKER_ID: "ci-coach-daily" @@ -7463,52 +7496,67 @@ jobs: core.info("Agent output content is empty"); } const ifNoChanges = process.env.GH_AW_PR_IF_NO_CHANGES || "warn"; + const allowEmpty = (process.env.GH_AW_PR_ALLOW_EMPTY || "false").toLowerCase() === "true"; if (!fs.existsSync("/tmp/gh-aw/aw.patch")) { - const message = "No patch file found - cannot create pull request without changes"; - if (isStaged) { - let summaryContent = "## 🎭 Staged Mode: Create Pull Request Preview\n\n"; - summaryContent += "The following pull request would be created if staged mode was disabled:\n\n"; - summaryContent += `**Status:** ⚠️ No patch file found\n\n`; - summaryContent += `**Message:** ${message}\n\n`; - await core.summary.addRaw(summaryContent).write(); - core.info("📝 Pull request creation preview written to step summary (no patch file)"); - return; - } - switch (ifNoChanges) { - case "error": - throw new Error(message); - case "ignore": - return; - case "warn": - default: - core.warning(message); + if (allowEmpty) { + core.info("No patch file found, but allow-empty is enabled - will create empty PR"); + } else { + const message = "No patch file found - cannot create pull request without changes"; + if (isStaged) { + let summaryContent = "## 🎭 Staged Mode: Create Pull Request Preview\n\n"; + summaryContent += "The following pull request would be created if staged mode was disabled:\n\n"; + summaryContent += `**Status:** ⚠️ No patch file found\n\n`; + summaryContent += `**Message:** ${message}\n\n`; + await core.summary.addRaw(summaryContent).write(); + core.info("📝 Pull request creation preview written to step summary (no patch file)"); return; + } + switch (ifNoChanges) { + case "error": + throw new Error(message); + case "ignore": + return; + case "warn": + default: + core.warning(message); + return; + } } } - const patchContent = fs.readFileSync("/tmp/gh-aw/aw.patch", "utf8"); + let patchContent = ""; + let isEmpty = true; + if (fs.existsSync("/tmp/gh-aw/aw.patch")) { + patchContent = fs.readFileSync("/tmp/gh-aw/aw.patch", "utf8"); + isEmpty = !patchContent || !patchContent.trim(); + } if (patchContent.includes("Failed to generate patch")) { - const message = "Patch file contains error message - cannot create pull request without changes"; - if (isStaged) { - let summaryContent = "## 🎭 Staged Mode: Create Pull Request Preview\n\n"; - summaryContent += "The following pull request would be created if staged mode was disabled:\n\n"; - summaryContent += `**Status:** ⚠️ Patch file contains error\n\n`; - summaryContent += `**Message:** ${message}\n\n`; - await core.summary.addRaw(summaryContent).write(); - core.info("📝 Pull request creation preview written to step summary (patch error)"); - return; - } - switch (ifNoChanges) { - case "error": - throw new Error(message); - case "ignore": - return; - case "warn": - default: - core.warning(message); + if (allowEmpty) { + core.info("Patch file contains error, but allow-empty is enabled - will create empty PR"); + patchContent = ""; + isEmpty = true; + } else { + const message = "Patch file contains error message - cannot create pull request without changes"; + if (isStaged) { + let summaryContent = "## 🎭 Staged Mode: Create Pull Request Preview\n\n"; + summaryContent += "The following pull request would be created if staged mode was disabled:\n\n"; + summaryContent += `**Status:** ⚠️ Patch file contains error\n\n`; + summaryContent += `**Message:** ${message}\n\n`; + await core.summary.addRaw(summaryContent).write(); + core.info("📝 Pull request creation preview written to step summary (patch error)"); return; + } + switch (ifNoChanges) { + case "error": + throw new Error(message); + case "ignore": + return; + case "warn": + default: + core.warning(message); + return; + } } } - const isEmpty = !patchContent || !patchContent.trim(); if (!isEmpty) { const maxSizeKb = parseInt(process.env.GH_AW_MAX_PATCH_SIZE || "1024", 10); const patchSizeBytes = Buffer.byteLength(patchContent, "utf8"); @@ -7529,7 +7577,7 @@ jobs: } core.info("Patch size validation passed"); } - if (isEmpty && !isStaged) { + if (isEmpty && !isStaged && !allowEmpty) { const message = "Patch file is empty - no changes to apply (noop operation)"; switch (ifNoChanges) { case "error": @@ -7545,6 +7593,8 @@ jobs: core.info(`Agent output content length: ${outputContent.length}`); if (!isEmpty) { core.info("Patch content validation passed"); + } else if (allowEmpty) { + core.info("Patch file is empty - processing empty PR creation (allow-empty is enabled)"); } else { core.info("Patch file is empty - processing noop operation"); } @@ -7754,16 +7804,44 @@ jobs: } } else { core.info("Skipping patch application (empty patch)"); - const message = "No changes to apply - noop operation completed successfully"; - switch (ifNoChanges) { - case "error": - throw new Error("No changes to apply - failing as configured by if-no-changes: error"); - case "ignore": - return; - case "warn": - default: - core.warning(message); + if (allowEmpty) { + core.info("allow-empty is enabled - will create branch and push without changes"); + try { + let remoteBranchExists = false; + try { + const { stdout } = await exec.getExecOutput(`git ls-remote --heads origin ${branchName}`); + if (stdout.trim()) { + remoteBranchExists = true; + } + } catch (checkError) { + core.info(`Remote branch check failed (non-fatal): ${checkError instanceof Error ? checkError.message : String(checkError)}`); + } + if (remoteBranchExists) { + core.warning(`Remote branch ${branchName} already exists - appending random suffix`); + const extraHex = crypto.randomBytes(4).toString("hex"); + const oldBranch = branchName; + branchName = `${branchName}-${extraHex}`; + await exec.exec(`git branch -m ${oldBranch} ${branchName}`); + core.info(`Renamed branch to ${branchName}`); + } + await exec.exec(`git push origin ${branchName}`); + core.info("Empty branch pushed successfully"); + } catch (pushError) { + core.setFailed(`Failed to push empty branch: ${pushError instanceof Error ? pushError.message : String(pushError)}`); return; + } + } else { + const message = "No changes to apply - noop operation completed successfully"; + switch (ifNoChanges) { + case "error": + throw new Error("No changes to apply - failing as configured by if-no-changes: error"); + case "ignore": + return; + case "warn": + default: + core.warning(message); + return; + } } } try { From e604803e08209564961033fd28cae48289a27f85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 01:38:12 +0000 Subject: [PATCH 3/3] Fix GH_TOKEN missing from make install step in shared/mcp/gh-aw.md Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- .github/workflows/audit-workflows.lock.yml | 4 +++- .github/workflows/cloclo.lock.yml | 4 +++- .github/workflows/daily-firewall-report.lock.yml | 4 +++- .github/workflows/deep-report.lock.yml | 4 +++- .github/workflows/dev-hawk.lock.yml | 4 +++- .github/workflows/mcp-inspector.lock.yml | 4 +++- .github/workflows/portfolio-analyst.lock.yml | 4 +++- .github/workflows/prompt-clustering-analysis.lock.yml | 4 +++- .github/workflows/q.lock.yml | 4 +++- .github/workflows/safe-output-health.lock.yml | 4 +++- .github/workflows/shared/mcp/gh-aw.md | 2 ++ .github/workflows/smoke-detector.lock.yml | 4 +++- .github/workflows/static-analysis-report.lock.yml | 4 +++- 13 files changed, 38 insertions(+), 12 deletions(-) diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 5004f6c88e1..8f36fb5cd45 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -1004,7 +1004,9 @@ jobs: go-version-file: go.mod - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 8001408870a..21a1eda678a 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -1912,7 +1912,9 @@ jobs: echo "Created /tmp/gh-aw/agent directory for agentic workflow temporary files" - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index 2d85079f90f..24776fcbd87 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -878,7 +878,9 @@ jobs: go-version-file: go.mod - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index d31ff3693ab..7c38e7396b9 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -782,7 +782,9 @@ jobs: go-version-file: go.mod - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index 620172ac12f..f666d619a8d 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -957,7 +957,9 @@ jobs: go-version-file: go.mod - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index debafcde433..14ddab76a75 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -570,7 +570,9 @@ jobs: echo "Created /tmp/gh-aw/agent directory for agentic workflow temporary files" - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/portfolio-analyst.lock.yml b/.github/workflows/portfolio-analyst.lock.yml index eb42b435187..d1bdf0d6fde 100644 --- a/.github/workflows/portfolio-analyst.lock.yml +++ b/.github/workflows/portfolio-analyst.lock.yml @@ -769,7 +769,9 @@ jobs: go-version-file: go.mod - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index 68faee4f7ac..117660a7b52 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -1274,7 +1274,9 @@ jobs: go-version-file: go.mod - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 0ec8f325d99..ff65d7df3d4 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -2032,7 +2032,9 @@ jobs: echo "Created /tmp/gh-aw/agent directory for agentic workflow temporary files" - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index a323871241d..498e3a50d04 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -756,7 +756,9 @@ jobs: go-version-file: go.mod - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/shared/mcp/gh-aw.md b/.github/workflows/shared/mcp/gh-aw.md index 9e52c4d6b4e..19f73fa8308 100644 --- a/.github/workflows/shared/mcp/gh-aw.md +++ b/.github/workflows/shared/mcp/gh-aw.md @@ -15,6 +15,8 @@ steps: run: make deps-dev - name: Install binary as 'gh-aw' run: make install + env: + GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - name: Start MCP server run: | set -e diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index 3bc7800c6a5..46ab4cad34a 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -1626,7 +1626,9 @@ jobs: go-version-file: go.mod - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index 5fb15b0e53e..104dc36b85c 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -671,7 +671,9 @@ jobs: go-version-file: go.mod - name: Install dependencies run: make deps-dev - - name: Install binary as 'gh-aw' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: Install binary as 'gh-aw' run: make install - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}