fix(doctor): avoid duplicate gateway runtime warnings (#79203)

This commit is contained in:
Peter Steinberger
2026-05-08 06:39:44 +01:00
committed by GitHub
parent 0a6818bbb5
commit bd3f09e969
3 changed files with 60 additions and 8 deletions

View File

@@ -185,6 +185,7 @@ Docs: https://docs.openclaw.ai
- Auto-reply/media: resolve `scp` from `PATH` when staging sandbox media so nonstandard OpenSSH installs can copy remote attachments.
- 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.
- Gateway/media: require authenticated owner or admin context for managed outgoing image bytes instead of trusting requester-session headers.
- Doctor/gateway: avoid duplicate Node runtime warnings when the daemon install plan already selected a supported Node runtime.
- 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.
- 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.
- 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.

View File

@@ -38,6 +38,9 @@ const mocks = vi.hoisted(() => ({
resolveIsNixMode: vi.fn(() => false),
findExtraGatewayServices: vi.fn().mockResolvedValue([]),
renderGatewayServiceCleanupHints: vi.fn().mockReturnValue([]),
needsNodeRuntimeMigration: vi.fn(() => false),
renderSystemNodeWarning: vi.fn().mockReturnValue(undefined),
resolveSystemNodeInfo: vi.fn().mockResolvedValue(null),
isSystemdUnitActive: vi.fn().mockResolvedValue(false),
uninstallLegacySystemdUnits: vi.fn().mockResolvedValue([]),
note: vi.fn(),
@@ -62,13 +65,13 @@ vi.mock("../daemon/inspect.js", () => ({
}));
vi.mock("../daemon/runtime-paths.js", () => ({
renderSystemNodeWarning: vi.fn().mockReturnValue(undefined),
resolveSystemNodeInfo: vi.fn().mockResolvedValue(null),
renderSystemNodeWarning: mocks.renderSystemNodeWarning,
resolveSystemNodeInfo: mocks.resolveSystemNodeInfo,
}));
vi.mock("../daemon/service-audit.js", () => ({
auditGatewayServiceConfig: mocks.auditGatewayServiceConfig,
needsNodeRuntimeMigration: vi.fn(() => false),
needsNodeRuntimeMigration: mocks.needsNodeRuntimeMigration,
readEmbeddedGatewayToken: readEmbeddedGatewayTokenForTest,
SERVICE_AUDIT_CODES: {
gatewayCommandMissing: testServiceAuditCodes.gatewayCommandMissing,
@@ -246,6 +249,9 @@ describe("maybeRepairGatewayServiceConfig", () => {
vi.clearAllMocks();
fsMocks.realpath.mockImplementation(async (value: string) => value);
mocks.resolveGatewayPort.mockReturnValue(18789);
mocks.needsNodeRuntimeMigration.mockReturnValue(false);
mocks.renderSystemNodeWarning.mockReturnValue(undefined);
mocks.resolveSystemNodeInfo.mockResolvedValue(null);
mocks.isSystemdUnitActive.mockResolvedValue(false);
mocks.resolveGatewayAuthTokenForService.mockImplementation(async (cfg: OpenClawConfig, env) => {
const configToken =
@@ -303,6 +309,50 @@ describe("maybeRepairGatewayServiceConfig", () => {
expect(mocks.install).toHaveBeenCalledTimes(1);
});
it("does not duplicate gateway runtime warnings already emitted by the node install plan", async () => {
const nvmNode = "/home/orin/.nvm/versions/node/v22.22.2/bin/node";
mocks.readCommand.mockResolvedValue({
programArguments: [nvmNode, "/usr/local/bin/openclaw", "gateway", "--port", "18789"],
environment: {},
});
mocks.buildGatewayInstallPlan.mockImplementation(async ({ warn }) => {
warn?.(
"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.",
"Gateway runtime",
);
return {
programArguments: [nvmNode, "/usr/local/bin/openclaw", "gateway", "--port", "18789"],
workingDirectory: "/tmp",
environment: {},
};
});
mocks.auditGatewayServiceConfig.mockResolvedValue({
ok: true,
issues: [{ code: "runtime", message: "runtime migration", level: "recommended" }],
});
mocks.needsNodeRuntimeMigration.mockReturnValue(true);
mocks.resolveSystemNodeInfo.mockResolvedValue({
path: "/usr/bin/node",
version: "20.20.2",
supported: false,
});
mocks.renderSystemNodeWarning.mockReturnValue("duplicate doctor runtime warning");
await runRepair({ gateway: {} });
const runtimeNotes = mocks.note.mock.calls.filter(([, title]) => title === "Gateway runtime");
const runtimeMessages = runtimeNotes.map(([message]) => message);
expect(runtimeMessages).not.toContain("duplicate doctor runtime warning");
expect(runtimeMessages).not.toEqual(
expect.arrayContaining([expect.stringContaining("not found")]),
);
expect(runtimeMessages).toEqual(
expect.arrayContaining([
expect.stringContaining("Using /home/orin/.nvm/versions/node/v22.22.2/bin/node"),
]),
);
});
it("passes planned managed env keys into service audit for legacy inline secret detection", async () => {
mocks.readCommand.mockResolvedValue({
programArguments: gatewayProgramArguments,

View File

@@ -422,15 +422,16 @@ export async function maybeRepairGatewayServiceConfig(
? await resolveSystemNodeInfo({ env: process.env })
: null;
const systemNodePath = systemNodeInfo?.supported ? systemNodeInfo.path : null;
if (needsNodeRuntime && !systemNodePath) {
if (needsNodeRuntime && !systemNodePath && runtimeChoice !== "node") {
const warning = renderSystemNodeWarning(systemNodeInfo);
if (warning) {
note(warning, "Gateway runtime");
} else {
note(
"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.",
"Gateway runtime",
);
}
note(
"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.",
"Gateway runtime",
);
}
const expectedRuntimePlan =