Skip to content

Commit bd3f09e

Browse files
authored
fix(doctor): avoid duplicate gateway runtime warnings (#79203)
1 parent 0a6818b commit bd3f09e

3 files changed

Lines changed: 60 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ Docs: https://docs.openclaw.ai
185185
- Auto-reply/media: resolve `scp` from `PATH` when staging sandbox media so nonstandard OpenSSH installs can copy remote attachments.
186186
- Agents/PI: route PI-native OpenAI-compatible default streams through OpenClaw boundary-aware transports so local-compatible model runs keep API-key injection and transport policy.
187187
- Gateway/media: require authenticated owner or admin context for managed outgoing image bytes instead of trusting requester-session headers.
188+
- Doctor/gateway: avoid duplicate Node runtime warnings when the daemon install plan already selected a supported Node runtime.
188189
- Gateway/watch: leave `OPENCLAW_TRACE_SYNC_IO` disabled by default in `pnpm gateway:watch:raw` so watch mode avoids noisy Node sync-I/O stack traces unless explicitly requested.
189190
- Codex app-server: close stdio stdin before force-killing the managed app-server, matching Codex single-client shutdown behavior and avoiding unsettled CLI exits after successful runs.
190191
- CLI/Codex: dispose registered agent harnesses during short-lived CLI shutdown so successful Codex-backed `agent --local` runs do not leave app-server child processes alive.

src/commands/doctor-gateway-services.test.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ const mocks = vi.hoisted(() => ({
3838
resolveIsNixMode: vi.fn(() => false),
3939
findExtraGatewayServices: vi.fn().mockResolvedValue([]),
4040
renderGatewayServiceCleanupHints: vi.fn().mockReturnValue([]),
41+
needsNodeRuntimeMigration: vi.fn(() => false),
42+
renderSystemNodeWarning: vi.fn().mockReturnValue(undefined),
43+
resolveSystemNodeInfo: vi.fn().mockResolvedValue(null),
4144
isSystemdUnitActive: vi.fn().mockResolvedValue(false),
4245
uninstallLegacySystemdUnits: vi.fn().mockResolvedValue([]),
4346
note: vi.fn(),
@@ -62,13 +65,13 @@ vi.mock("../daemon/inspect.js", () => ({
6265
}));
6366

6467
vi.mock("../daemon/runtime-paths.js", () => ({
65-
renderSystemNodeWarning: vi.fn().mockReturnValue(undefined),
66-
resolveSystemNodeInfo: vi.fn().mockResolvedValue(null),
68+
renderSystemNodeWarning: mocks.renderSystemNodeWarning,
69+
resolveSystemNodeInfo: mocks.resolveSystemNodeInfo,
6770
}));
6871

6972
vi.mock("../daemon/service-audit.js", () => ({
7073
auditGatewayServiceConfig: mocks.auditGatewayServiceConfig,
71-
needsNodeRuntimeMigration: vi.fn(() => false),
74+
needsNodeRuntimeMigration: mocks.needsNodeRuntimeMigration,
7275
readEmbeddedGatewayToken: readEmbeddedGatewayTokenForTest,
7376
SERVICE_AUDIT_CODES: {
7477
gatewayCommandMissing: testServiceAuditCodes.gatewayCommandMissing,
@@ -246,6 +249,9 @@ describe("maybeRepairGatewayServiceConfig", () => {
246249
vi.clearAllMocks();
247250
fsMocks.realpath.mockImplementation(async (value: string) => value);
248251
mocks.resolveGatewayPort.mockReturnValue(18789);
252+
mocks.needsNodeRuntimeMigration.mockReturnValue(false);
253+
mocks.renderSystemNodeWarning.mockReturnValue(undefined);
254+
mocks.resolveSystemNodeInfo.mockResolvedValue(null);
249255
mocks.isSystemdUnitActive.mockResolvedValue(false);
250256
mocks.resolveGatewayAuthTokenForService.mockImplementation(async (cfg: OpenClawConfig, env) => {
251257
const configToken =
@@ -303,6 +309,50 @@ describe("maybeRepairGatewayServiceConfig", () => {
303309
expect(mocks.install).toHaveBeenCalledTimes(1);
304310
});
305311

312+
it("does not duplicate gateway runtime warnings already emitted by the node install plan", async () => {
313+
const nvmNode = "/home/orin/.nvm/versions/node/v22.22.2/bin/node";
314+
mocks.readCommand.mockResolvedValue({
315+
programArguments: [nvmNode, "/usr/local/bin/openclaw", "gateway", "--port", "18789"],
316+
environment: {},
317+
});
318+
mocks.buildGatewayInstallPlan.mockImplementation(async ({ warn }) => {
319+
warn?.(
320+
"System Node 20.20.2 at /usr/bin/node is below the required Node 22.16+. Using /home/orin/.nvm/versions/node/v22.22.2/bin/node for the daemon.",
321+
"Gateway runtime",
322+
);
323+
return {
324+
programArguments: [nvmNode, "/usr/local/bin/openclaw", "gateway", "--port", "18789"],
325+
workingDirectory: "/tmp",
326+
environment: {},
327+
};
328+
});
329+
mocks.auditGatewayServiceConfig.mockResolvedValue({
330+
ok: true,
331+
issues: [{ code: "runtime", message: "runtime migration", level: "recommended" }],
332+
});
333+
mocks.needsNodeRuntimeMigration.mockReturnValue(true);
334+
mocks.resolveSystemNodeInfo.mockResolvedValue({
335+
path: "/usr/bin/node",
336+
version: "20.20.2",
337+
supported: false,
338+
});
339+
mocks.renderSystemNodeWarning.mockReturnValue("duplicate doctor runtime warning");
340+
341+
await runRepair({ gateway: {} });
342+
343+
const runtimeNotes = mocks.note.mock.calls.filter(([, title]) => title === "Gateway runtime");
344+
const runtimeMessages = runtimeNotes.map(([message]) => message);
345+
expect(runtimeMessages).not.toContain("duplicate doctor runtime warning");
346+
expect(runtimeMessages).not.toEqual(
347+
expect.arrayContaining([expect.stringContaining("not found")]),
348+
);
349+
expect(runtimeMessages).toEqual(
350+
expect.arrayContaining([
351+
expect.stringContaining("Using /home/orin/.nvm/versions/node/v22.22.2/bin/node"),
352+
]),
353+
);
354+
});
355+
306356
it("passes planned managed env keys into service audit for legacy inline secret detection", async () => {
307357
mocks.readCommand.mockResolvedValue({
308358
programArguments: gatewayProgramArguments,

src/commands/doctor-gateway-services.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -422,15 +422,16 @@ export async function maybeRepairGatewayServiceConfig(
422422
? await resolveSystemNodeInfo({ env: process.env })
423423
: null;
424424
const systemNodePath = systemNodeInfo?.supported ? systemNodeInfo.path : null;
425-
if (needsNodeRuntime && !systemNodePath) {
425+
if (needsNodeRuntime && !systemNodePath && runtimeChoice !== "node") {
426426
const warning = renderSystemNodeWarning(systemNodeInfo);
427427
if (warning) {
428428
note(warning, "Gateway runtime");
429+
} else {
430+
note(
431+
"System Node 22 LTS (22.16+) or Node 24 not found. Install via Homebrew/apt/choco and rerun doctor to migrate off Bun/version managers.",
432+
"Gateway runtime",
433+
);
429434
}
430-
note(
431-
"System Node 22 LTS (22.16+) or Node 24 not found. Install via Homebrew/apt/choco and rerun doctor to migrate off Bun/version managers.",
432-
"Gateway runtime",
433-
);
434435
}
435436

436437
const expectedRuntimePlan =

0 commit comments

Comments
 (0)