fix(gateway): harden service-mode stale process cleanup (#38463, thanks @spirittechie)

Co-authored-by: Jesse Paul <drzin69@gmail.com>
This commit is contained in:
Peter Steinberger
2026-03-07 21:36:00 +00:00
parent 1835d5808f
commit cc7e61612a
5 changed files with 41 additions and 2 deletions

View File

@@ -253,9 +253,12 @@ function waitForPortFreeSync(port: number): void {
*
* Called before service restart commands to prevent port conflicts.
*/
export function cleanStaleGatewayProcessesSync(): number[] {
export function cleanStaleGatewayProcessesSync(portOverride?: number): number[] {
try {
const port = resolveGatewayPort(undefined, process.env);
const port =
typeof portOverride === "number" && Number.isFinite(portOverride) && portOverride > 0
? Math.floor(portOverride)
: resolveGatewayPort(undefined, process.env);
const stalePids = findGatewayPidsOnPortSync(port);
if (stalePids.length === 0) {
return [];

View File

@@ -95,6 +95,27 @@ describe.runIf(process.platform !== "win32")("cleanStaleGatewayProcessesSync", (
expect(killSpy).toHaveBeenCalledWith(6002, "SIGKILL");
});
it("uses explicit port override when provided", () => {
spawnSyncMock.mockReturnValue({
error: undefined,
status: 0,
stdout: ["p7001", "copenclaw"].join("\n"),
});
const killSpy = vi.spyOn(process, "kill").mockImplementation(() => true);
const killed = cleanStaleGatewayProcessesSync(19999);
expect(killed).toEqual([7001]);
expect(resolveGatewayPortMock).not.toHaveBeenCalled();
expect(spawnSyncMock).toHaveBeenCalledWith(
"/usr/sbin/lsof",
["-nP", "-iTCP:19999", "-sTCP:LISTEN", "-Fpc"],
expect.objectContaining({ encoding: "utf8", timeout: 2000 }),
);
expect(killSpy).toHaveBeenCalledWith(7001, "SIGTERM");
expect(killSpy).toHaveBeenCalledWith(7001, "SIGKILL");
});
it("returns empty when no stale listeners are found", () => {
spawnSyncMock.mockReturnValue({
error: undefined,