Skip to content

Bundle MCP runtime never released for isolated cron sessions — unbounded process leak #69145

Description

@swordsman-guardian

Summary

When cron jobs use sessionTarget: "isolated" with payload.kind: "agentTurn", each run spawns a full set of bundle MCP stdio child processes (one per configured MCP server), but these processes are never released after the run completes. This causes unbounded process accumulation and memory growth.

Version

OpenClaw 2026.4.15 (041266a)

Reproduction

  1. Configure multiple MCP servers in openclaw.json (e.g. searxng, crawl4ai, filesystem, memory, sqlite, git, context7, sequential-thinking, time)
  2. Create a cron job with sessionTarget: "isolated" and payload.kind: "agentTurn"
  3. Set it to run frequently (e.g. */3 * * * *)
  4. Observe: each cron run spawns ~9 new Node.js child processes (one per MCP server), each consuming ~100 MB RSS
  5. After N runs: N × (num_mcp_servers) orphan processes, never garbage-collected

Root Cause Analysis

After tracing the code paths in the dist/ bundle:

  1. MCP runtime creation (pi-bundle-mcp-tools-Diapg7vD.js):

    • getOrCreateSessionMcpRuntime() creates a SessionMcpRuntime that spawns stdio child processes for every configured MCP server
    • Runtime is keyed by sessionId and cached in runtimesBySessionId Map
    • disposeSessionMcpRuntime(sessionId) properly kills all child processes
  2. Cron execution path (server.impl-STMC_0uD.jspi-embedded-runner-CefZK1Pt.js):

    • Each isolated cron job creates a new session with a unique sessionId
    • runEmbeddedPiAgent() calls getOrCreateSessionMcpRuntime() → spawns all MCP processes
    • runEmbeddedPiAgent() receives cleanupBundleMcpOnRunEnd = undefined (never set to true)
    • The finally block at line 9144 only disposes runtime when cleanupBundleMcpOnRunEnd === true
  3. Reply path (get-reply-Bt3IWYIA.js):

    • disposeSessionMcpRuntime(previousSessionEntry.sessionId) is only called when a session reset occurs
    • Normal message turns reuse the same session → no new MCP processes spawned
    • But isolated cron sessions are always new → always spawn new processes
  4. Only the CLI --local path (register.agent-9kfgbSik.js line 115) sets cleanupBundleMcpOnRunEnd: true

Impact

With 9 MCP servers and a */3 * * * isolated cron job:

  • Each run: ~9 new Node processes × ~100 MB RSS = ~900 MB
  • After 1 hour (20 runs): ~180 processes, ~18 GB RSS
  • Processes accumulate indefinitely; only a gateway restart clears them

Evidence

On a live system after ~10 minutes of uptime:

  • searxng-mcp: 6 instances (~580 MB total)
  • context7-mcp: 6 instances (~600 MB total)
  • git-mcp-server: 6 instances (~870 MB total)
  • playwright/mcp Docker containers: 6 instances
  • github-mcp-server Docker containers: 6 instances
  • All other MCP servers: 6 instances each
  • Session store: 58 entries, each with unique sessionId

Expected Behavior

  • Cron runs with sessionTarget: "isolated" should release MCP runtimes after completion
  • OR: isolated cron sessions should reuse a single MCP runtime keyed by job ID, not session ID
  • Session maintenance prune/cap should also dispose the associated MCP runtimes

Suggested Fixes

  1. Pass cleanupBundleMcpOnRunEnd: true in the cron execution path (server.impl-STMC_0uD.js) so the finally block in runEmbeddedPiAgent disposes the runtime
  2. Dispose runtimes during session maintenance when entries are pruned or capped
  3. Cache isolated session runtimes by cron job ID instead of sessionId to reuse processes across runs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions