gateway: narrow already-running exit code (#26718)

Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
This commit is contained in:
yuna78 2026-03-30 07:59:32 +08:00 committed by GitHub
parent 2885c65c74
commit 0033f64e19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 66 additions and 9 deletions

View File

@ -228,16 +228,59 @@ describe("gateway-cli coverage", () => {
});
it("prints stop hints on GatewayLockError when service is loaded", async () => {
await withEnvOverride(
{
LAUNCH_JOB_LABEL: undefined,
LAUNCH_JOB_NAME: undefined,
XPC_SERVICE_NAME: undefined,
OPENCLAW_LAUNCHD_LABEL: undefined,
OPENCLAW_SYSTEMD_UNIT: undefined,
INVOCATION_ID: undefined,
SYSTEMD_EXEC_PID: undefined,
JOURNAL_STREAM: undefined,
OPENCLAW_WINDOWS_TASK_NAME: undefined,
OPENCLAW_SERVICE_MARKER: undefined,
OPENCLAW_SERVICE_KIND: undefined,
},
async () => {
resetRuntimeCapture();
serviceIsLoaded.mockResolvedValue(true);
startGatewayServer.mockRejectedValueOnce(
new GatewayLockError("another gateway instance is already listening"),
);
await expect(
runGatewayCommand(["gateway", "--token", "test-token", "--allow-unconfigured"]),
).rejects.toThrow("__exit__:0");
expect(startGatewayServer).toHaveBeenCalled();
expect(runtimeErrors.join("\n")).toContain("Gateway failed to start:");
expect(runtimeErrors.join("\n")).toContain("gateway stop");
},
);
});
it("keeps exit 1 for gateway bind failures wrapped as GatewayLockError", async () => {
resetRuntimeCapture();
serviceIsLoaded.mockResolvedValue(true);
startGatewayServer.mockRejectedValueOnce(
new GatewayLockError("another gateway instance is already listening"),
new GatewayLockError("failed to bind gateway socket on ws://127.0.0.1:18789: Error: boom"),
);
await expectGatewayExit(["gateway", "--token", "test-token", "--allow-unconfigured"]);
expect(startGatewayServer).toHaveBeenCalled();
expect(runtimeErrors.join("\n")).toContain("Gateway failed to start:");
expect(runtimeErrors.join("\n")).toContain("gateway stop");
expect(runtimeErrors.join("\n")).toContain("failed to bind gateway socket");
});
it("keeps exit 1 for gateway lock acquisition failures", async () => {
resetRuntimeCapture();
serviceIsLoaded.mockResolvedValue(true);
startGatewayServer.mockRejectedValueOnce(
new GatewayLockError("failed to acquire gateway lock at /tmp/openclaw/gateway.lock"),
);
await expectGatewayExit(["gateway", "--token", "test-token", "--allow-unconfigured"]);
expect(runtimeErrors.join("\n")).toContain("failed to acquire gateway lock");
});
it("uses env/config port when --port is omitted", async () => {

View File

@ -162,6 +162,23 @@ function resolveGatewayRunOptions(opts: GatewayRunOpts, command?: Command): Gate
return resolved;
}
function isGatewayLockError(err: unknown): err is GatewayLockError {
return (
err instanceof GatewayLockError ||
(!!err && typeof err === "object" && (err as { name?: string }).name === "GatewayLockError")
);
}
function isHealthyGatewayLockError(err: unknown): boolean {
if (!isGatewayLockError(err) || typeof err.message !== "string") {
return false;
}
return (
err.message.includes("gateway already running") ||
err.message.includes("another gateway instance is already listening")
);
}
async function runGatewayCommand(opts: GatewayRunOpts) {
const isDevProfile = process.env.OPENCLAW_PROFILE?.trim().toLowerCase() === "dev";
const devMode = Boolean(opts.dev) || isDevProfile;
@ -457,10 +474,7 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
}
}
} catch (err) {
if (
err instanceof GatewayLockError ||
(err && typeof err === "object" && (err as { name?: string }).name === "GatewayLockError")
) {
if (isGatewayLockError(err)) {
const errMessage = describeUnknownError(err);
defaultRuntime.error(
`Gateway failed to start: ${errMessage}\nIf the gateway is supervised, stop it with: ${formatCliCommand("openclaw gateway stop")}`,
@ -476,7 +490,7 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
// ignore diagnostics failures
}
await maybeExplainGatewayServiceStop();
defaultRuntime.exit(1);
defaultRuntime.exit(isHealthyGatewayLockError(err) ? 0 : 1);
return;
}
defaultRuntime.error(`Gateway failed to start: ${String(err)}`);