diff --git a/src/cli/gateway-cli/run.option-collisions.test.ts b/src/cli/gateway-cli/run.option-collisions.test.ts index ffaeab97d5b..e2eab032038 100644 --- a/src/cli/gateway-cli/run.option-collisions.test.ts +++ b/src/cli/gateway-cli/run.option-collisions.test.ts @@ -34,6 +34,11 @@ const recoverConfigFromJsonRootSuffix = vi.fn<(snapshot?: unknown) => Promise Promise>( async (_payload?: unknown) => "/tmp/restart-sentinel.json", ); +const writeDiagnosticStabilityBundleForFailureSync = vi.fn((_reason: string, _error: unknown) => ({ + status: "written" as const, + message: "wrote stability bundle: /tmp/openclaw-stability.json", + path: "/tmp/openclaw-stability.json", +})); const controlUiState = vi.hoisted(() => ({ root: "/tmp/openclaw-control-ui" as string | null, })); @@ -110,6 +115,11 @@ vi.mock("../../logging/console.js", () => ({ setConsoleTimestampPrefix: () => undefined, })); +vi.mock("../../logging/diagnostic-stability-bundle.js", () => ({ + writeDiagnosticStabilityBundleForFailureSync: (reason: string, error: unknown) => + writeDiagnosticStabilityBundleForFailureSync(reason, error), +})); + vi.mock("../../logging/subsystem.js", () => ({ createSubsystemLogger: () => ({ info: (message: string) => { @@ -167,6 +177,7 @@ describe("gateway run option collisions", () => { recoverConfigFromJsonRootSuffix.mockResolvedValue(false); writeRestartSentinel.mockReset(); writeRestartSentinel.mockResolvedValue("/tmp/restart-sentinel.json"); + writeDiagnosticStabilityBundleForFailureSync.mockClear(); startGatewayServer.mockClear(); setGatewayWsLogStyle.mockClear(); setVerbose.mockClear(); @@ -253,6 +264,19 @@ describe("gateway run option collisions", () => { ); }); + it("does not write startup failure bundles for expected gateway lock conflicts", async () => { + const err = Object.assign(new Error("gateway already running on port 18789"), { + name: "GatewayLockError", + }); + startGatewayServer.mockRejectedValueOnce(err); + + await expect(runGatewayCli(["gateway", "run", "--allow-unconfigured"])).rejects.toThrow( + "__exit__:0", + ); + + expect(writeDiagnosticStabilityBundleForFailureSync).not.toHaveBeenCalled(); + }); + it("blocks startup when the observed snapshot loses gateway.mode even if loadConfig still says local", async () => { configState.cfg = { gateway: { diff --git a/src/cli/gateway-cli/run.ts b/src/cli/gateway-cli/run.ts index 5e659430ece..a3561f3863b 100644 --- a/src/cli/gateway-cli/run.ts +++ b/src/cli/gateway-cli/run.ts @@ -686,7 +686,6 @@ async function runGatewayCommand(opts: GatewayRunOpts) { } } } catch (err) { - maybeWriteGatewayStartupFailureBundle(err); if (isGatewayLockError(err)) { const errMessage = formatErrorMessage(err); defaultRuntime.error( @@ -706,6 +705,7 @@ async function runGatewayCommand(opts: GatewayRunOpts) { defaultRuntime.exit(isHealthyGatewayLockError(err) ? 0 : 1); return; } + maybeWriteGatewayStartupFailureBundle(err); defaultRuntime.error(`Gateway failed to start: ${String(err)}`); defaultRuntime.exit(1); }