Skip to content

Commit c6a8ad8

Browse files
Gio Della-Liberagiodl73-repo
authored andcommitted
fix(cli): reject invalid node run port
1 parent 6e9d47b commit c6a8ad8

3 files changed

Lines changed: 80 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ Docs: https://docs.openclaw.ai
129129

130130
### Fixes
131131

132+
- CLI/node: reject invalid explicit `node run --port` values instead of silently falling back to the configured or default port. Fixes #83923. Thanks @davinci282828.
132133
- CLI: reject explicit port numbers above 65535 before they reach Gateway or Node bind paths. Fixes #83900. (#84008) Thanks @hclsys.
133134
- Codex app-server: preserve plugin tool auth profiles when Codex owns model transport so OpenClaw dynamic tools can resolve their provider credentials. (#83603) Thanks @rubencu.
134135
- Memory/search: scan the JS-side fallback vector path (used when the sqlite-vec index is unavailable or has a mismatched dimension) in bounded rowid batches and yield to the event loop between batches so large chunk tables can no longer pin the Node.js main thread for multi-second windows. Also keeps the SQL prepared statement rooted in a local so node:sqlite cannot finalize it mid-scan under heap pressure. Fixes #81172. Thanks @dev23xyz-oss.

src/cli/node-cli/register.test.ts

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import { Command } from "commander";
2-
import { describe, expect, it, vi } from "vitest";
2+
import { beforeEach, describe, expect, it, vi } from "vitest";
33
import { registerNodeCli } from "./register.js";
44

5+
type LoadNodeHostConfig = typeof import("../../node-host/config.js").loadNodeHostConfig;
6+
57
const daemonMocks = vi.hoisted(() => ({
8+
defaultRuntime: {
9+
error: vi.fn(),
10+
exit: vi.fn(),
11+
},
12+
loadNodeHostConfig: vi.fn<LoadNodeHostConfig>(async () => null),
13+
runNodeHost: vi.fn(),
614
runNodeDaemonInstall: vi.fn(),
715
runNodeDaemonRestart: vi.fn(),
816
runNodeDaemonStart: vi.fn(),
@@ -14,11 +22,15 @@ const daemonMocks = vi.hoisted(() => ({
1422
vi.mock("./daemon.js", () => daemonMocks);
1523

1624
vi.mock("../../node-host/config.js", () => ({
17-
loadNodeHostConfig: vi.fn(async () => null),
25+
loadNodeHostConfig: daemonMocks.loadNodeHostConfig,
1826
}));
1927

2028
vi.mock("../../node-host/runner.js", () => ({
21-
runNodeHost: vi.fn(),
29+
runNodeHost: daemonMocks.runNodeHost,
30+
}));
31+
32+
vi.mock("../../runtime.js", () => ({
33+
defaultRuntime: daemonMocks.defaultRuntime,
2234
}));
2335

2436
function createProgram(): Command {
@@ -33,11 +45,62 @@ function createProgram(): Command {
3345
}
3446

3547
describe("registerNodeCli", () => {
48+
beforeEach(() => {
49+
daemonMocks.defaultRuntime.error.mockClear();
50+
daemonMocks.defaultRuntime.exit.mockClear();
51+
daemonMocks.loadNodeHostConfig.mockClear();
52+
daemonMocks.loadNodeHostConfig.mockResolvedValue(null);
53+
daemonMocks.runNodeHost.mockClear();
54+
daemonMocks.runNodeDaemonInstall.mockClear();
55+
daemonMocks.runNodeDaemonRestart.mockClear();
56+
daemonMocks.runNodeDaemonStart.mockClear();
57+
daemonMocks.runNodeDaemonStatus.mockClear();
58+
daemonMocks.runNodeDaemonStop.mockClear();
59+
daemonMocks.runNodeDaemonUninstall.mockClear();
60+
});
61+
3662
it("registers node start for the macOS app node service manager", async () => {
3763
const program = createProgram();
3864

3965
await program.parseAsync(["node", "start", "--json"], { from: "user" });
4066

4167
expect(daemonMocks.runNodeDaemonStart.mock.calls[0]?.[0]?.json).toBe(true);
4268
});
69+
70+
it("rejects an explicit invalid node run port", async () => {
71+
const program = createProgram();
72+
73+
await program.parseAsync(["node", "run", "--port", "abc"], { from: "user" });
74+
75+
expect(daemonMocks.runNodeHost).not.toHaveBeenCalled();
76+
expect(daemonMocks.defaultRuntime.error).toHaveBeenCalledWith(
77+
expect.stringContaining("Invalid --port"),
78+
);
79+
expect(daemonMocks.defaultRuntime.exit).toHaveBeenCalledWith(1);
80+
});
81+
82+
it("uses an explicit valid node run port", async () => {
83+
const program = createProgram();
84+
85+
await program.parseAsync(["node", "run", "--port", "19000"], { from: "user" });
86+
87+
expect(daemonMocks.runNodeHost).toHaveBeenCalledWith(
88+
expect.objectContaining({ gatewayPort: 19000 }),
89+
);
90+
});
91+
92+
it("falls back to configured node run port when --port is omitted", async () => {
93+
daemonMocks.loadNodeHostConfig.mockResolvedValue({
94+
version: 1,
95+
nodeId: "node-existing",
96+
gateway: { host: "10.0.0.2", port: 19001 },
97+
});
98+
const program = createProgram();
99+
100+
await program.parseAsync(["node", "run"], { from: "user" });
101+
102+
expect(daemonMocks.runNodeHost).toHaveBeenCalledWith(
103+
expect.objectContaining({ gatewayHost: "10.0.0.2", gatewayPort: 19001 }),
104+
);
105+
});
43106
});

src/cli/node-cli/register.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import type { Command } from "commander";
22
import { loadNodeHostConfig } from "../../node-host/config.js";
33
import { runNodeHost } from "../../node-host/runner.js";
4+
import { defaultRuntime } from "../../runtime.js";
45
import { normalizeOptionalString } from "../../shared/string-coerce.js";
56
import { formatDocsLink } from "../../terminal/links.js";
67
import { theme } from "../../terminal/theme.js";
78
import { parsePort } from "../daemon-cli/shared.js";
9+
import { formatInvalidPortOption } from "../error-format.js";
810
import { formatHelpExamples } from "../help-format.js";
911
import {
1012
runNodeDaemonInstall,
@@ -15,9 +17,11 @@ import {
1517
runNodeDaemonUninstall,
1618
} from "./daemon.js";
1719

18-
function parsePortWithFallback(value: unknown, fallback: number): number {
19-
const parsed = parsePort(value);
20-
return parsed ?? fallback;
20+
function parsePortOption(value: unknown, fallback: number): number | null {
21+
if (value === undefined) {
22+
return fallback;
23+
}
24+
return parsePort(value);
2125
}
2226

2327
export function registerNodeCli(program: Command) {
@@ -54,7 +58,12 @@ export function registerNodeCli(program: Command) {
5458
normalizeOptionalString(opts.host as string | undefined) ||
5559
existing?.gateway?.host ||
5660
"127.0.0.1";
57-
const port = parsePortWithFallback(opts.port, existing?.gateway?.port ?? 18789);
61+
const port = parsePortOption(opts.port, existing?.gateway?.port ?? 18789);
62+
if (port === null) {
63+
defaultRuntime.error(formatInvalidPortOption("--port"));
64+
defaultRuntime.exit(1);
65+
return;
66+
}
5867
await runNodeHost({
5968
gatewayHost: host,
6069
gatewayPort: port,

0 commit comments

Comments
 (0)