diff --git a/CHANGELOG.md b/CHANGELOG.md index f11e157f44b..e5bdf045cd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- MCP/CLI: retire bundled MCP runtimes at the end of one-shot `openclaw agent` and `openclaw infer model run` gateway/local executions, so repeated scripted runs do not accumulate stdio MCP child processes. Fixes #71457. - OpenAI/Codex image generation: canonicalize legacy `openai-codex.baseUrl` values such as `https://chatgpt.com/backend-api` to the Codex Responses backend before calling `gpt-image-2`, matching the chat transport. Fixes #71460. - Control UI: make `/usage` use the fresh context snapshot for context percentage, and include cache-write tokens in the Usage overview cache-hit denominator. Fixes #47885. Thanks @imwyvern and @Ante042. - GitHub Copilot: preserve encrypted Responses reasoning item IDs during replay diff --git a/docs/cli/agent.md b/docs/cli/agent.md index ef1c165eb5e..e0119c082a1 100644 --- a/docs/cli/agent.md +++ b/docs/cli/agent.md @@ -52,6 +52,7 @@ openclaw agent --agent ops --message "Run locally" --local - Gateway mode falls back to the embedded agent when the Gateway request fails. Use `--local` to force embedded execution up front. - `--local` still preloads the plugin registry first, so plugin-provided providers, tools, and channels stay available during embedded runs. +- Each `openclaw agent` invocation is treated as a one-shot run. Bundled or user-configured MCP servers opened for that run are retired after the reply, even when the command uses the Gateway path, so stdio MCP child processes do not stay alive between scripted invocations. - `--channel`, `--reply-channel`, and `--reply-account` affect reply delivery, not session routing. - `--json` keeps stdout reserved for the JSON response. Gateway, plugin, and embedded-fallback diagnostics are routed to stderr so scripts can parse stdout directly. - When this command triggers `models.json` regeneration, SecretRef-managed provider credentials are persisted as non-secret markers (for example env var names, `secretref-env:ENV_VAR_NAME`, or `secretref-managed`), not resolved secret plaintext. diff --git a/docs/cli/infer.md b/docs/cli/infer.md index 99716fa6f0d..c7c7b221b98 100644 --- a/docs/cli/infer.md +++ b/docs/cli/infer.md @@ -130,6 +130,7 @@ This table maps common inference tasks to the corresponding infer command. - Stateless execution commands default to local. - Gateway-managed state commands default to gateway. - The normal local path does not require the gateway to be running. +- `model run` is one-shot. MCP servers opened through the agent runtime for that command are retired after the reply for both local and `--gateway` execution, so repeated scripted invocations do not keep stdio MCP child processes alive. ## Model @@ -145,6 +146,7 @@ openclaw infer model inspect --name gpt-5.5 --json Notes: - `model run` reuses the agent runtime so provider/model overrides behave like normal agent execution. +- Because `model run` is intended for headless automation, it does not retain per-session bundled MCP runtimes after the command finishes. - `model auth login`, `model auth logout`, and `model auth status` manage saved provider auth state. ## Image diff --git a/docs/cli/mcp.md b/docs/cli/mcp.md index 77a6d64b8cb..4f98e70af2b 100644 --- a/docs/cli/mcp.md +++ b/docs/cli/mcp.md @@ -61,6 +61,10 @@ Important behavior: - older transcript history is read with `messages_read` - Claude push notifications only exist while the MCP session is alive - when the client disconnects, the bridge exits and the live queue is gone +- one-shot agent entry points such as `openclaw agent` and + `openclaw infer model run` retire any bundled MCP runtimes they open when the + reply completes, so repeated scripted runs do not accumulate stdio MCP child + processes - stdio MCP servers launched by OpenClaw (bundled or user-configured) are torn down as a process tree on shutdown, so child subprocesses started by the server do not survive after the parent stdio client exits diff --git a/src/commands/agent-via-gateway.test.ts b/src/commands/agent-via-gateway.test.ts index 254e44b0bc7..02e99f17930 100644 --- a/src/commands/agent-via-gateway.test.ts +++ b/src/commands/agent-via-gateway.test.ts @@ -117,6 +117,11 @@ describe("agentCliCommand", () => { await agentCliCommand({ message: "hi", to: "+1555" }, runtime); expect(callGateway).toHaveBeenCalledTimes(1); + expect(callGateway.mock.calls[0]?.[0]).toMatchObject({ + params: { + cleanupBundleMcpOnRunEnd: true, + }, + }); expect(agentCommand).not.toHaveBeenCalled(); expect(runtime.log).toHaveBeenCalledWith("hello"); }); @@ -198,7 +203,7 @@ describe("agentCliCommand", () => { }); }); - it("does not force bundle MCP cleanup on gateway fallback", async () => { + it("forces bundle MCP cleanup on embedded fallback", async () => { await withTempStore(async () => { callGateway.mockRejectedValue(new Error("gateway not connected")); mockLocalAgentReply(); @@ -206,7 +211,7 @@ describe("agentCliCommand", () => { await agentCliCommand({ message: "hi", to: "+1555" }, runtime); expect(agentCommand).toHaveBeenCalledTimes(1); - expect(agentCommand.mock.calls[0]?.[0]).not.toMatchObject({ + expect(agentCommand.mock.calls[0]?.[0]).toMatchObject({ cleanupBundleMcpOnRunEnd: true, }); }); diff --git a/src/commands/agent-via-gateway.ts b/src/commands/agent-via-gateway.ts index e3b7de264aa..a993e7341bd 100644 --- a/src/commands/agent-via-gateway.ts +++ b/src/commands/agent-via-gateway.ts @@ -152,6 +152,7 @@ export async function agentViaGatewayCommand(opts: AgentCliOpts, runtime: Runtim bestEffortDeliver: opts.bestEffortDeliver, timeout: timeoutSeconds, lane: opts.lane, + cleanupBundleMcpOnRunEnd: true, extraSystemPrompt: opts.extraSystemPrompt, idempotencyKey, }, @@ -191,7 +192,7 @@ export async function agentCliCommand(opts: AgentCliOpts, runtime: RuntimeEnv, d ...opts, agentId: opts.agent, replyAccountId: opts.replyAccount, - cleanupBundleMcpOnRunEnd: opts.local === true, + cleanupBundleMcpOnRunEnd: true, }; if (opts.local === true) { return await agentCommand(localOpts, runtime, deps);