Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
OpenClaw Version: 2026.4.15 (041266a)
Platform: Ubuntu 24.04, systemd user service (openclaw-gateway)
Affects: Multi-agent fleet setups with agentToAgent.enabled: true and per-agent MCP servers configured in openclaw.json
Summary
Every call to sessions_send targeting another agent leaks a full cohort of MCP child processes. With 9 agents configured, baseline is 9 MCP children after a clean gateway start. One sessions_send causes 9 additional MCP processes to spawn and the original cohort is never cleaned up. The leak is deterministic and reproduces 100% of the time.
Steps to reproduce
Repro Steps
Start fresh gateway:
systemctl --user restart openclaw-gateway
Confirm baseline — 1 gateway parent + 9 MCP children:
pgrep -a -f "mcp/server.py"
→ 9 processes (one per configured agent)
Send one minimal cross-agent message via sessions_send to any agent (e.g. Spark), reply REPRO_OK
Check processes again:
pgrep -a -f "mcp/server.py"
→ 18 processes — original 9 still running, 9 new ones added
Confirmed repro with fresh gateway PID 179821:
Baseline MCP children: 179966 179969 179972 179977 179982 179985 179988 179991 179994
After one sessions_send to Spark: 180076 180079 180082 180089 180092 180095 180098 180101 180104
Original cohort still running — not cleaned up
Repeats on every sessions_send. Each call adds another full cohort.
Root Cause (Code-Level)
The cleanup flag cleanupBundleMcpOnRunEnd is only ever set to true in local/embedded mode.
register.agent-COPfBHma.js, line 115:
cleanupBundleMcpOnRunEnd: opts.local === true
The cleanup itself lives in pi-embedded-runner-DN0VbqlW.js, line 9713:
if (params.cleanupBundleMcpOnRunEnd === true)
await disposeSessionMcpRuntime(params.sessionId).catch(...)
When sessions_send triggers a nested agent run via runAgentStep() in subagent-registry-BrNWizSY.js:
const response = await agentStepDeps.callGateway({
method: "agent",
params: {
lane: params.lane ?? AGENT_LANE_NESTED,
...
}
});
This goes through the gateway path, not --local. cleanupBundleMcpOnRunEnd is false (or unset). The finally block that calls disposeSessionMcpRuntime never fires. The MCP child processes spawned for that nested session's runtime are never cleaned up.
server.impl-GQ72oJBa.js — the gateway implementation — does not reference cleanupBundleMcpOnRunEnd at all.
The SessionMcpRuntimeManager (pi-bundle-mcp-tools-vusm-AE2.js, line 483) correctly tracks runtimes by sessionId and has a working disposeSession() method — but it is never called for gateway-path nested sessions because the flag that triggers it is hardcoded to local === true.
Expected behavior
Impact
Fleet setups with cross-agent communication via sessions_send will accumulate MCP child processes indefinitely
Each sessions_send = one leaked cohort (N processes, where N = number of configured MCP servers)
Gateway eventually becomes unstable under load; agent-to-agent comms degrade
Only workaround is periodic systemctl --user restart openclaw-gateway to reset process count
Actual behavior
Root Cause (Code-Level)
The cleanup flag cleanupBundleMcpOnRunEnd is only ever set to true in local/embedded mode.
register.agent-COPfBHma.js, line 115:
cleanupBundleMcpOnRunEnd: opts.local === true
The cleanup itself lives in pi-embedded-runner-DN0VbqlW.js, line 9713:
if (params.cleanupBundleMcpOnRunEnd === true)
await disposeSessionMcpRuntime(params.sessionId).catch(...)
When sessions_send triggers a nested agent run via runAgentStep() in subagent-registry-BrNWizSY.js:
const response = await agentStepDeps.callGateway({
method: "agent",
params: {
lane: params.lane ?? AGENT_LANE_NESTED,
...
}
});
This goes through the gateway path, not --local. cleanupBundleMcpOnRunEnd is false (or unset). The finally block that calls disposeSessionMcpRuntime never fires. The MCP child processes spawned for that nested session's runtime are never cleaned up.
server.impl-GQ72oJBa.js — the gateway implementation — does not reference cleanupBundleMcpOnRunEnd at all.
The SessionMcpRuntimeManager (pi-bundle-mcp-tools-vusm-AE2.js, line 483) correctly tracks runtimes by sessionId and has a working disposeSession() method — but it is never called for gateway-path nested sessions because the flag that triggers it is hardcoded to local === true.
OpenClaw version
Version: 2026.4.15 (041266a)
Operating system
Platform: Ubuntu 24.04, systemd user service (openclaw-gateway)
Install method
No response
Model
GPT-5.4
Provider / routing chain
openclaw
Additional provider/model setup details
Provider config during repro:
- main (Jarvis): openai-codex/gpt-5.4 primary,
fallbacks: openrouter/minimax/minimax-m2.7, openrouter/google/gemini-2.5-flash
- All other agents: openrouter/minimax/minimax-m2.7 primary,
fallback: openrouter/google/gemini-2.5-flash
- sessions_send target during repro: Spark (openrouter/minimax/minimax-m2.7)
- Bug also confirmed cross-agent: Jarvis → Atlas, Jarvis → Forge
- Provider chain does not appear relevant — leak is in gateway MCP lifecycle,
not inference path
Logs, screenshots, and evidence
Suggested Fix
The gateway needs to call disposeSessionMcpRuntime(sessionId) when a nested/ephemeral agent run ends. Two options:
Option A — Set cleanupBundleMcpOnRunEnd: true for nested lane runs in the gateway's agent handler (similar to how --local sets it):
cleanupBundleMcpOnRunEnd: opts.local === true || opts.lane === AGENT_LANE_NESTED
Option B — Wire disposeSessionMcpRuntime into the gateway's session lifecycle end handler (onSessionLifecycleEvent in subagent-registry-BrNWizSY.js) so cleanup fires on session end regardless of how the run was initiated.
Option B is more robust as it handles any future code paths that spawn nested sessions without going through agentCliCommand.
Additional Notes
pkill -f mcp/server.py clears child symptoms but does not fix the leak — it reproduces immediately on next sessions_send
socat processes are a red herring — confirmed the real unit is the user service
Logs around repro show [agent:nested], ANNOUNCE_SKIP, and repeated webchat reconnect churn consistent with leaked runtimes
agentToAgent.enabled: true and tools.sessions.visibility: all are set in openclaw.json
Impact and severity
No response
Additional information
No response
Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
OpenClaw Version: 2026.4.15 (041266a)
Platform: Ubuntu 24.04, systemd user service (openclaw-gateway)
Affects: Multi-agent fleet setups with agentToAgent.enabled: true and per-agent MCP servers configured in openclaw.json
Summary
Every call to sessions_send targeting another agent leaks a full cohort of MCP child processes. With 9 agents configured, baseline is 9 MCP children after a clean gateway start. One sessions_send causes 9 additional MCP processes to spawn and the original cohort is never cleaned up. The leak is deterministic and reproduces 100% of the time.
Steps to reproduce
Repro Steps
Start fresh gateway:
systemctl --user restart openclaw-gateway
Confirm baseline — 1 gateway parent + 9 MCP children:
pgrep -a -f "mcp/server.py"
→ 9 processes (one per configured agent)
Send one minimal cross-agent message via sessions_send to any agent (e.g. Spark), reply REPRO_OK
Check processes again:
pgrep -a -f "mcp/server.py"
→ 18 processes — original 9 still running, 9 new ones added
Confirmed repro with fresh gateway PID 179821:
Baseline MCP children: 179966 179969 179972 179977 179982 179985 179988 179991 179994
After one sessions_send to Spark: 180076 180079 180082 180089 180092 180095 180098 180101 180104
Original cohort still running — not cleaned up
Repeats on every sessions_send. Each call adds another full cohort.
Root Cause (Code-Level)
The cleanup flag cleanupBundleMcpOnRunEnd is only ever set to true in local/embedded mode.
register.agent-COPfBHma.js, line 115:
cleanupBundleMcpOnRunEnd: opts.local === true
The cleanup itself lives in pi-embedded-runner-DN0VbqlW.js, line 9713:
if (params.cleanupBundleMcpOnRunEnd === true)
await disposeSessionMcpRuntime(params.sessionId).catch(...)
When sessions_send triggers a nested agent run via runAgentStep() in subagent-registry-BrNWizSY.js:
const response = await agentStepDeps.callGateway({
method: "agent",
params: {
lane: params.lane ?? AGENT_LANE_NESTED,
...
}
});
This goes through the gateway path, not --local. cleanupBundleMcpOnRunEnd is false (or unset). The finally block that calls disposeSessionMcpRuntime never fires. The MCP child processes spawned for that nested session's runtime are never cleaned up.
server.impl-GQ72oJBa.js — the gateway implementation — does not reference cleanupBundleMcpOnRunEnd at all.
The SessionMcpRuntimeManager (pi-bundle-mcp-tools-vusm-AE2.js, line 483) correctly tracks runtimes by sessionId and has a working disposeSession() method — but it is never called for gateway-path nested sessions because the flag that triggers it is hardcoded to local === true.
Expected behavior
Impact
Fleet setups with cross-agent communication via sessions_send will accumulate MCP child processes indefinitely
Each sessions_send = one leaked cohort (N processes, where N = number of configured MCP servers)
Gateway eventually becomes unstable under load; agent-to-agent comms degrade
Only workaround is periodic systemctl --user restart openclaw-gateway to reset process count
Actual behavior
Root Cause (Code-Level)
The cleanup flag cleanupBundleMcpOnRunEnd is only ever set to true in local/embedded mode.
register.agent-COPfBHma.js, line 115:
cleanupBundleMcpOnRunEnd: opts.local === true
The cleanup itself lives in pi-embedded-runner-DN0VbqlW.js, line 9713:
if (params.cleanupBundleMcpOnRunEnd === true)
await disposeSessionMcpRuntime(params.sessionId).catch(...)
When sessions_send triggers a nested agent run via runAgentStep() in subagent-registry-BrNWizSY.js:
const response = await agentStepDeps.callGateway({
method: "agent",
params: {
lane: params.lane ?? AGENT_LANE_NESTED,
...
}
});
This goes through the gateway path, not --local. cleanupBundleMcpOnRunEnd is false (or unset). The finally block that calls disposeSessionMcpRuntime never fires. The MCP child processes spawned for that nested session's runtime are never cleaned up.
server.impl-GQ72oJBa.js — the gateway implementation — does not reference cleanupBundleMcpOnRunEnd at all.
The SessionMcpRuntimeManager (pi-bundle-mcp-tools-vusm-AE2.js, line 483) correctly tracks runtimes by sessionId and has a working disposeSession() method — but it is never called for gateway-path nested sessions because the flag that triggers it is hardcoded to local === true.
OpenClaw version
Version: 2026.4.15 (041266a)
Operating system
Platform: Ubuntu 24.04, systemd user service (openclaw-gateway)
Install method
No response
Model
GPT-5.4
Provider / routing chain
openclaw
Additional provider/model setup details
Provider config during repro:
fallbacks: openrouter/minimax/minimax-m2.7, openrouter/google/gemini-2.5-flash
fallback: openrouter/google/gemini-2.5-flash
not inference path
Logs, screenshots, and evidence
Impact and severity
No response
Additional information
No response