From 0356948d359af9e0a17ba01f78f32cd27391be95 Mon Sep 17 00:00:00 2001 From: Nimrod Gutman Date: Thu, 12 Mar 2026 11:20:22 +0200 Subject: [PATCH] fix(doctor): harden entrypoint path normalization --- src/commands/doctor-gateway-services.test.ts | 71 ++++++++++++++++++++ src/commands/doctor-gateway-services.ts | 2 +- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/commands/doctor-gateway-services.test.ts b/src/commands/doctor-gateway-services.test.ts index 813b5c4f183..7809f6b003d 100644 --- a/src/commands/doctor-gateway-services.test.ts +++ b/src/commands/doctor-gateway-services.test.ts @@ -279,6 +279,77 @@ describe("maybeRepairGatewayServiceConfig", () => { expect(mocks.install).not.toHaveBeenCalled(); }); + it("does not flag entrypoint mismatch when realpath fails but normalized absolute paths match", async () => { + mocks.readCommand.mockResolvedValue({ + programArguments: [ + "/usr/bin/node", + "/opt/openclaw/../openclaw/dist/index.js", + "gateway", + "--port", + "18789", + ], + environment: {}, + }); + mocks.auditGatewayServiceConfig.mockResolvedValue({ + ok: true, + issues: [], + }); + mocks.buildGatewayInstallPlan.mockResolvedValue({ + programArguments: [ + "/usr/bin/node", + "/opt/openclaw/dist/index.js", + "gateway", + "--port", + "18789", + ], + environment: {}, + }); + fsMocks.realpath.mockRejectedValue(new Error("no realpath")); + + await runRepair({ gateway: {} }); + + expect(mocks.note).not.toHaveBeenCalledWith( + expect.stringContaining("Gateway service entrypoint does not match the current install."), + "Gateway service config", + ); + expect(mocks.install).not.toHaveBeenCalled(); + }); + + it("still flags entrypoint mismatch when canonicalized paths differ", async () => { + mocks.readCommand.mockResolvedValue({ + programArguments: [ + "/usr/bin/node", + "/Users/test/.nvm/versions/node/v22.0.0/lib/node_modules/openclaw/dist/index.js", + "gateway", + "--port", + "18789", + ], + environment: {}, + }); + mocks.auditGatewayServiceConfig.mockResolvedValue({ + ok: true, + issues: [], + }); + mocks.buildGatewayInstallPlan.mockResolvedValue({ + programArguments: [ + "/usr/bin/node", + "/Users/test/Library/pnpm/global/5/node_modules/openclaw/dist/index.js", + "gateway", + "--port", + "18789", + ], + environment: {}, + }); + + await runRepair({ gateway: {} }); + + expect(mocks.note).toHaveBeenCalledWith( + expect.stringContaining("Gateway service entrypoint does not match the current install."), + "Gateway service config", + ); + expect(mocks.install).toHaveBeenCalledTimes(1); + }); + it("treats SecretRef-managed gateway token as non-persisted service state", async () => { mocks.readCommand.mockResolvedValue({ programArguments: gatewayProgramArguments, diff --git a/src/commands/doctor-gateway-services.ts b/src/commands/doctor-gateway-services.ts index 38378b4b822..4a6d0fca8ca 100644 --- a/src/commands/doctor-gateway-services.ts +++ b/src/commands/doctor-gateway-services.ts @@ -55,7 +55,7 @@ function findGatewayEntrypoint(programArguments?: string[]): string | null { } async function normalizeExecutablePath(value: string): Promise { - const resolvedPath = path.isAbsolute(value) ? value : path.resolve(value); + const resolvedPath = path.resolve(value); try { return await fs.realpath(resolvedPath); } catch {