diff --git a/docs/cli/config.md b/docs/cli/config.md index eaf945d88d6c..f6a63c1adeb1 100644 --- a/docs/cli/config.md +++ b/docs/cli/config.md @@ -461,7 +461,7 @@ After every successful `config set` / `config patch` / `config unset`, the CLI p | `Change will apply without restarting the gateway.` | Hot reload picks it up automatically. | | `No gateway restart needed.` | Nothing runtime-relevant changed. | -Writes to `plugins.entries` (or any subpath) always require a restart, since the CLI cannot prove every plugin's reload metadata is loaded. +Effective changes to `plugins.entries` (or any subpath) require a restart, since the CLI cannot prove every plugin's reload metadata is loaded. Idempotent writes with no effective diff report `No gateway restart needed.` ## Write safety diff --git a/src/cli/config-cli-runner.ts b/src/cli/config-cli-runner.ts index 510e1ade0883..36389a8127e7 100644 --- a/src/cli/config-cli-runner.ts +++ b/src/cli/config-cli-runner.ts @@ -235,10 +235,10 @@ export function configApplyHintForOperations( beforeConfig, afterConfig, ); - if ( - paths.length === 0 || - paths.some((path) => path === "plugins.entries" || path.startsWith("plugins.entries.")) - ) { + if (paths.length === 0) { + return "No gateway restart needed."; + } + if (paths.some((path) => path === "plugins.entries" || path.startsWith("plugins.entries."))) { return "Restart the gateway to apply."; } const plan = buildGatewayReloadPlan(paths, { candidateConfig: afterConfig }); diff --git a/src/cli/config-cli.test.ts b/src/cli/config-cli.test.ts index 36044fd6f928..4152fc472366 100644 --- a/src/cli/config-cli.test.ts +++ b/src/cli/config-cli.test.ts @@ -4215,6 +4215,35 @@ describe("config cli", () => { }); describe("config apply hints - issue #80722", () => { + it("prints a no-restart hint for a same-value config set", async () => { + setGatewaySnapshot(); + + await runConfigSet("gateway.port", "18789", "--strict-json"); + + expect(mockWriteConfigFile).toHaveBeenCalledTimes(1); + expectLogIncludes("Updated gateway.port. No gateway restart needed."); + expectLogExcludes("Restart the gateway to apply."); + expectLogExcludes("Change will apply without restarting the gateway."); + }); + + it("prints a no-restart hint for a same-value config patch", async () => { + setGatewaySnapshot(); + const pathname = writeTempJson5File("openclaw-config-patch-same-value", { + gateway: { port: 18789 }, + }); + + try { + await runConfigCommand(["config", "patch", "--file", pathname]); + } finally { + fs.rmSync(pathname, { force: true }); + } + + expect(mockWriteConfigFile).toHaveBeenCalledTimes(1); + expectLogIncludes("Applied 1 config update(s). No gateway restart needed."); + expectLogExcludes("Restart the gateway to apply."); + expectLogExcludes("Change will apply without restarting the gateway."); + }); + it("prints a hot-reload hint for agents.list model changes", async () => { const resolved: OpenClawConfig = { agents: { @@ -4429,6 +4458,7 @@ describe("config cli", () => { expectLogIncludes("Updated plugins.entries.canvas.enabled"); expectLogIncludes("Restart the gateway to apply."); expectLogExcludes("Change will apply without restarting the gateway."); + expectLogExcludes("No gateway restart needed."); }); it("keeps the restart hint for mixed hot and restart batch updates", async () => {