diff --git a/actions/setup/js/run_operation_update_upgrade.cjs b/actions/setup/js/run_operation_update_upgrade.cjs index 4098e9ab2bb..bd36efa89f0 100644 --- a/actions/setup/js/run_operation_update_upgrade.cjs +++ b/actions/setup/js/run_operation_update_upgrade.cjs @@ -63,10 +63,14 @@ async function main() { const isUpgrade = operation === "upgrade"; - // Run gh aw update or gh aw upgrade (--no-compile: do not touch lock files) - const fullCmd = [bin, ...prefixArgs, operation, "--no-compile"].join(" "); + // Run gh aw update or gh aw upgrade without extra flags so all files are + // updated (codemods, action pins, lock files, etc.). Changed files under + // .github/workflows/ are detected afterwards but excluded from staging so + // the GitHub Actions actor – which is not permitted to commit workflow + // files – does not attempt to include them in the pull request. + const fullCmd = [bin, ...prefixArgs, operation].join(" "); core.info(`Running: ${fullCmd}`); - const exitCode = await exec.exec(bin, [...prefixArgs, operation, "--no-compile"]); + const exitCode = await exec.exec(bin, [...prefixArgs, operation]); if (exitCode !== 0) { throw new Error(`Command '${fullCmd}' failed with exit code ${exitCode}`); } @@ -93,11 +97,12 @@ async function main() { return; } - // Exclude .github/workflows/*.yml files: they cannot be modified by the - // GitHub Actions bot and including them would cause the PR checks to fail. + // Exclude ALL .github/workflows/ files: the GitHub Actions actor is not + // permitted to commit any changes to workflow files (neither compiled .yml + // files nor source .md files). Including them would cause PR checks to fail. const filesToStage = changedFiles.filter(file => { const lower = file.toLowerCase(); - return !(lower.startsWith(".github/workflows/") && (lower.endsWith(".yml") || lower.endsWith(".yaml"))); + return !lower.startsWith(".github/workflows/"); }); if (filesToStage.length === 0) { @@ -184,7 +189,7 @@ async function main() { const operationLabel = isUpgrade ? "Upgrade" : "Update"; const prBody = `## Agentic Workflows ${operationLabel} -The \`gh aw ${operation} --no-compile\` command was run automatically and produced the following changes: +The \`gh aw ${operation}\` command was run automatically and produced the following changes: ${fileList} diff --git a/actions/setup/js/run_operation_update_upgrade.test.cjs b/actions/setup/js/run_operation_update_upgrade.test.cjs index 2a7d05d8d96..5f1645f741c 100644 --- a/actions/setup/js/run_operation_update_upgrade.test.cjs +++ b/actions/setup/js/run_operation_update_upgrade.test.cjs @@ -183,7 +183,7 @@ describe("run_operation_update_upgrade", () => { await main(); expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("No changes detected")); - expect(mockExec.exec).toHaveBeenCalledWith("gh", ["aw", "update", "--no-compile"]); + expect(mockExec.exec).toHaveBeenCalledWith("gh", ["aw", "update"]); }); it("finishes without PR when only workflow yml files changed", async () => { @@ -203,6 +203,24 @@ describe("run_operation_update_upgrade", () => { expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("No non-workflow files changed")); expect(mockExec.exec).not.toHaveBeenCalledWith("git", expect.arrayContaining(["add"])); }); + + it("finishes without PR when only workflow md files changed", async () => { + process.env.GH_AW_OPERATION = "update"; + process.env.GH_AW_CMD_PREFIX = "gh aw"; + process.env.GH_TOKEN = "test-token"; + + mockExec.getExecOutput = vi.fn().mockResolvedValueOnce({ + stdout: " M .github/workflows/my-workflow.md\n", + stderr: "", + exitCode: 0, + }); + + const { main } = await import("./run_operation_update_upgrade.cjs"); + await main(); + + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("No non-workflow files changed")); + expect(mockExec.exec).not.toHaveBeenCalledWith("git", expect.arrayContaining(["add"])); + }); }); describe("main - creates PR when files changed", () => { @@ -212,15 +230,15 @@ describe("run_operation_update_upgrade", () => { process.env.GH_TOKEN = "test-token"; const getExecOutputMock = vi.fn(); - // git status + // git status - only non-workflow file changed getExecOutputMock.mockResolvedValueOnce({ - stdout: " M .github/workflows/my-workflow.md\n", + stdout: " M .github/aw/actions-lock.json\n", stderr: "", exitCode: 0, }); // git diff --cached --name-only getExecOutputMock.mockResolvedValueOnce({ - stdout: ".github/workflows/my-workflow.md\n", + stdout: ".github/aw/actions-lock.json\n", stderr: "", exitCode: 0, }); @@ -235,12 +253,12 @@ describe("run_operation_update_upgrade", () => { const { main } = await import("./run_operation_update_upgrade.cjs"); await main(); - // Verify gh aw update --no-compile was run - expect(mockExec.exec).toHaveBeenCalledWith("gh", ["aw", "update", "--no-compile"]); + // Verify gh aw update was run + expect(mockExec.exec).toHaveBeenCalledWith("gh", ["aw", "update"]); // Verify branch was created expect(mockExec.exec).toHaveBeenCalledWith("git", expect.arrayContaining(["checkout", "-b", expect.stringContaining("aw/update-")])); // Verify file was staged - expect(mockExec.exec).toHaveBeenCalledWith("git", ["add", "--", ".github/workflows/my-workflow.md"]); + expect(mockExec.exec).toHaveBeenCalledWith("git", ["add", "--", ".github/aw/actions-lock.json"]); // Verify commit was made expect(mockExec.exec).toHaveBeenCalledWith("git", ["commit", "-m", "chore: update agentic workflows"]); // Verify PR title @@ -248,6 +266,41 @@ describe("run_operation_update_upgrade", () => { expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Created PR")); }); + it("creates PR with only non-workflow files when both workflow and non-workflow files changed", async () => { + process.env.GH_AW_OPERATION = "update"; + process.env.GH_AW_CMD_PREFIX = "gh aw"; + process.env.GH_TOKEN = "test-token"; + + // Both a workflow .md and a non-workflow file changed + const getExecOutputMock = vi.fn(); + getExecOutputMock.mockResolvedValueOnce({ + stdout: " M .github/workflows/my-workflow.md\n M .github/aw/actions-lock.json\n", + stderr: "", + exitCode: 0, + }); + // git diff --cached --name-only (only non-workflow file staged) + getExecOutputMock.mockResolvedValueOnce({ + stdout: ".github/aw/actions-lock.json\n", + stderr: "", + exitCode: 0, + }); + // gh pr create + getExecOutputMock.mockResolvedValueOnce({ + stdout: "https://github.com/testowner/testrepo/pull/5\n", + stderr: "", + exitCode: 0, + }); + mockExec.getExecOutput = getExecOutputMock; + + const { main } = await import("./run_operation_update_upgrade.cjs"); + await main(); + + // Workflow .md must NOT be staged + expect(mockExec.exec).not.toHaveBeenCalledWith("git", ["add", "--", ".github/workflows/my-workflow.md"]); + // Non-workflow file should be staged + expect(mockExec.exec).toHaveBeenCalledWith("git", ["add", "--", ".github/aw/actions-lock.json"]); + }); + it("creates PR for upgrade operation with correct title", async () => { process.env.GH_AW_OPERATION = "upgrade"; process.env.GH_AW_CMD_PREFIX = "gh aw"; @@ -277,8 +330,8 @@ describe("run_operation_update_upgrade", () => { const { main } = await import("./run_operation_update_upgrade.cjs"); await main(); - // Verify gh aw upgrade --no-compile was run - expect(mockExec.exec).toHaveBeenCalledWith("gh", ["aw", "upgrade", "--no-compile"]); + // Verify gh aw upgrade was run + expect(mockExec.exec).toHaveBeenCalledWith("gh", ["aw", "upgrade"]); // Verify correct commit message expect(mockExec.exec).toHaveBeenCalledWith("git", ["commit", "-m", "chore: upgrade agentic workflows"]); // Verify PR title is "[aw] Upgrade available" @@ -294,16 +347,16 @@ describe("run_operation_update_upgrade", () => { const getExecOutputMock = vi.fn(); getExecOutputMock - .mockResolvedValueOnce({ stdout: " M .github/workflows/my-workflow.md\n", stderr: "", exitCode: 0 }) - .mockResolvedValueOnce({ stdout: ".github/workflows/my-workflow.md\n", stderr: "", exitCode: 0 }) + .mockResolvedValueOnce({ stdout: " M .github/aw/actions-lock.json\n", stderr: "", exitCode: 0 }) + .mockResolvedValueOnce({ stdout: ".github/aw/actions-lock.json\n", stderr: "", exitCode: 0 }) .mockResolvedValueOnce({ stdout: "https://github.com/testowner/testrepo/pull/3\n", stderr: "", exitCode: 0 }); mockExec.getExecOutput = getExecOutputMock; const { main } = await import("./run_operation_update_upgrade.cjs"); await main(); - // Verify binary is ./gh-aw (no prefix args) with --no-compile - expect(mockExec.exec).toHaveBeenCalledWith("./gh-aw", ["update", "--no-compile"]); + // Verify binary is ./gh-aw (no prefix args) + expect(mockExec.exec).toHaveBeenCalledWith("./gh-aw", ["update"]); }); }); @@ -338,7 +391,7 @@ describe("run_operation_update_upgrade", () => { const getExecOutputMock = vi.fn(); getExecOutputMock .mockResolvedValueOnce({ - stdout: " M .github/workflows/my-workflow.md\n?? .github/aw/actions-lock.json\n", + stdout: " M .github/agents/agentic-workflows.agent.md\n?? .github/aw/actions-lock.json\n", stderr: "", exitCode: 0, }) @@ -348,7 +401,7 @@ describe("run_operation_update_upgrade", () => { // git add fails for the first file, succeeds for others mockExec.exec = vi.fn().mockImplementation(async (cmd, args) => { - if (cmd === "git" && args[0] === "add" && args[2] === ".github/workflows/my-workflow.md") { + if (cmd === "git" && args[0] === "add" && args[2] === ".github/agents/agentic-workflows.agent.md") { throw new Error("git add failed"); } return 0;