diff --git a/.changeset/fix-wrangler-signal-exit-code.md b/.changeset/fix-wrangler-signal-exit-code.md new file mode 100644 index 0000000000..8b7fcb7fb5 --- /dev/null +++ b/.changeset/fix-wrangler-signal-exit-code.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +Fix `wrangler` reporting exit code 0 when its child process is killed by a signal (e.g. OOM/SIGKILL) + +Previously, if the underlying `wrangler` process was terminated by a signal rather than exiting normally, the CLI wrapper reported exit code 0, making CI pipelines and other automation believe the command succeeded. `wrangler` now reports a non-zero exit code and logs the signal in this case. diff --git a/packages/wrangler/bin/wrangler.js b/packages/wrangler/bin/wrangler.js index 774ecc2ce6..696486e631 100755 --- a/packages/wrangler/bin/wrangler.js +++ b/packages/wrangler/bin/wrangler.js @@ -5,6 +5,16 @@ const path = require("path"); const MIN_NODE_VERSION = "22.0.0"; let wranglerProcess; +function resolveExitCode(code, signal) { + if (code !== null && code !== undefined) { + return code; + } + if (signal) { + console.error(`wrangler exited because it received signal ${signal}`); + } + return 1; +} + /** * Executes ../wrangler-dist/cli.js */ @@ -33,9 +43,7 @@ Consider using a Node.js version manager such as https://volta.sh/ or https://gi stdio: ["inherit", "inherit", "inherit", "ipc"], } ) - .on("exit", (code) => - process.exit(code === undefined || code === null ? 0 : code) - ) + .on("exit", (code, signal) => process.exit(resolveExitCode(code, signal))) .on("message", (message) => { if (process.send) { process.send(message); @@ -89,3 +97,5 @@ if (module === require.main) { wranglerProcess && wranglerProcess.kill(); }); } + +module.exports = { resolveExitCode }; diff --git a/packages/wrangler/src/__tests__/bin-wrangler-exit-code.test.ts b/packages/wrangler/src/__tests__/bin-wrangler-exit-code.test.ts new file mode 100644 index 0000000000..3c23176018 --- /dev/null +++ b/packages/wrangler/src/__tests__/bin-wrangler-exit-code.test.ts @@ -0,0 +1,30 @@ +import { describe, it } from "vitest"; +import { mockConsoleMethods } from "./helpers/mock-console"; + +// eslint-disable-next-line @typescript-eslint/no-require-imports -- bin/wrangler.js is a plain CommonJS script +const { resolveExitCode } = require("../../bin/wrangler.js"); + +describe("bin/wrangler.js resolveExitCode", () => { + const std = mockConsoleMethods(); + + it("passes through a normal numeric exit code", ({ expect }) => { + expect(resolveExitCode(0, null)).toBe(0); + expect(resolveExitCode(1, null)).toBe(1); + }); + + it("reports a non-zero exit code when killed by a signal", ({ expect }) => { + expect(resolveExitCode(null, "SIGKILL")).toBe(1); + expect(std.err).toContain("SIGKILL"); + }); + + it("reports a non-zero exit code for SIGTERM", ({ expect }) => { + expect(resolveExitCode(null, "SIGTERM")).toBe(1); + expect(std.err).toContain("SIGTERM"); + }); + + it("defaults to a non-zero exit code when code and signal are both missing", ({ + expect, + }) => { + expect(resolveExitCode(undefined, null)).toBe(1); + }); +});