diff --git a/src/node/services/workflows/WorkflowActionRunner.test.ts b/src/node/services/workflows/WorkflowActionRunner.test.ts index 3cdb4b61f6..afdcafb721 100644 --- a/src/node/services/workflows/WorkflowActionRunner.test.ts +++ b/src/node/services/workflows/WorkflowActionRunner.test.ts @@ -202,6 +202,67 @@ describe("WorkflowActionRunner", () => { }); }); + test("keeps ctx.exec output out of action diagnostics", async () => { + using tmp = new DisposableTempDir("workflow-action-exec-diagnostics"); + const sourcePath = path.join(tmp.path, "exec-diagnostics.js"); + const source = ` + module.exports.metadata = { version: 1, description: "Exec diagnostics", effect: "read" }; + module.exports.execute = async (_input, ctx) => { + console.log("action log"); + const result = await ctx.exec(process.execPath, ["-e", "process.stdout.write('cmd stdout'); process.stderr.write('cmd stderr')"]); + return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode }; + }; + `; + await fs.writeFile(sourcePath, source, "utf-8"); + const runner = new WorkflowActionRunner(); + + const result = await runner.execute(createAction(sourcePath, source), { + input: null, + cwd: tmp.path, + timeoutMs: 10_000, + artifactDir: path.join(tmp.path, "artifacts"), + }); + + expect(result.output).toEqual({ stdout: "cmd stdout", stderr: "cmd stderr", exitCode: 0 }); + expect(result.stdout).toBe("action log\n"); + expect(result.stderr).toBe(""); + }); + + test("keeps cleanup ps warnings out of action diagnostics", async () => { + if (process.platform === "win32") { + return; + } + using tmp = new DisposableTempDir("workflow-action-ps-diagnostics"); + const fakeBinDir = path.join(tmp.path, "bin"); + const fakePsPath = path.join(fakeBinDir, "ps"); + const sourcePath = path.join(tmp.path, "ps-diagnostics.js"); + const sentinel = "fake ps warning should stay hidden"; + await fs.mkdir(fakeBinDir, { recursive: true }); + await fs.writeFile(fakePsPath, `#!/bin/sh\nprintf '${sentinel}\\n' >&2\n`, "utf-8"); + await fs.chmod(fakePsPath, 0o755); + const source = ` + module.exports.metadata = { version: 1, description: "Ps diagnostics", effect: "read" }; + module.exports.execute = async (_input, ctx) => { + process.env.PATH = ${JSON.stringify(fakeBinDir + path.delimiter)} + (process.env.PATH || ""); + const result = await ctx.exec(process.execPath, ["-e", ""]); + return { exitCode: result.exitCode }; + }; + `; + await fs.writeFile(sourcePath, source, "utf-8"); + const runner = new WorkflowActionRunner(); + + const result = await runner.execute(createAction(sourcePath, source), { + input: null, + cwd: tmp.path, + timeoutMs: 10_000, + artifactDir: path.join(tmp.path, "artifacts"), + }); + + expect(result.output).toEqual({ exitCode: 0 }); + expect(result.stderr).not.toContain(sentinel); + expect(result.stderr).toBe(""); + }); + test("built-in git actions reject truncated command output", async () => { using tmp = new DisposableTempDir("workflow-action-git-truncated"); const repoRoot = path.join(tmp.path, "repo"); diff --git a/src/node/services/workflows/WorkflowActionRunner.ts b/src/node/services/workflows/WorkflowActionRunner.ts index 34925724e2..d32d176169 100644 --- a/src/node/services/workflows/WorkflowActionRunner.ts +++ b/src/node/services/workflows/WorkflowActionRunner.ts @@ -992,7 +992,10 @@ function captureResult(capture) { function listChildPids(pid) { try { - return execFileSync("ps", ["-axo", "pid=,ppid="], { encoding: "utf-8" }) + return execFileSync("ps", ["-axo", "pid=,ppid="], { + encoding: "utf-8", + stdio: ["ignore", "pipe", "ignore"], + }) .trim() .split(/\n+/) .map((line) => line.trim().split(/\s+/).map((value) => Number(value))) @@ -1328,11 +1331,9 @@ async function execCommand(command, args = [], options = {}) { timer?.unref?.(); child.stdout?.on("data", (chunk) => { appendCapture(stdout, chunk); - process.stdout.write(chunk); }); child.stderr?.on("data", (chunk) => { appendCapture(stderr, chunk); - process.stderr.write(chunk); }); child.on("exit", (code, childSignal) => { exitCode = code; diff --git a/src/node/services/workflows/WorkflowRunner.test.ts b/src/node/services/workflows/WorkflowRunner.test.ts index 24215f9d00..4a07caa09e 100644 --- a/src/node/services/workflows/WorkflowRunner.test.ts +++ b/src/node/services/workflows/WorkflowRunner.test.ts @@ -1806,6 +1806,9 @@ describe("WorkflowRunner", () => { expect(parsed.ignored).toContain("ignored.txt"); expect(parsed.branchFiles).toContain("feature.txt"); expect(parsed.branchDiff).toContain("feature.txt"); + expect(parsed.branchDiff).toContain("diff --git"); + expect(parsed.branchDiff).toContain("+feature"); + expect(parsed.branchStat).not.toContain("diff --git"); expect(parsed.unstagedDiff).toContain("+dirty"); expect(parsed.diffTruncated).toEqual({ branch: false, staged: false, unstaged: false }); expect(parsed.branchStat).toContain("feature.txt");