Skip to content

[Bug]: MCP child process leak: sessions_send via gateway never calls disposeSessionMcpRuntime #70364

Description

@aiedvlyman

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingbug:behaviorIncorrect behavior without a crash

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions