[BUG] MCP server child processes (stdio transport) survive when a scheduled/background task run ends, leaking connections indefinitely

Status Open
Reported on v2.1.237
Maintainer reply None cached
Activity 1 comment · opened Aug 25, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

MCP server child processes (stdio transport, e.g. a Python-wrapped server binary) spawned by a Claude Code scheduled/cron task run are not terminated when that run's claude.exe process exits. They keep running indefinitely afterward, still holding whatever resources they opened (in our case, live PostgreSQL connections), because they apparently never receive an EOF/shutdown signal on stdin once their parent is gone.

Over about 6 days, one recurring cron-scheduled task (firing every 2 hours via the built-in scheduled-tasks feature, and unrelated in purpose to the MCP server involved) left 54 orphaned MCP server processes running. Each still held an open database connection, which eventually exhausted the target PostgreSQL server's max_connections limit and caused an unrelated application connecting to the same server to fail with error 53300 ("too many connections").

What Should Happen?

When a Claude Code process exits - whether from an interactive session or a scheduled/background task run - every MCP server process (and its descendants) that it spawned should be reliably terminated too, regardless of how that process exits (graceful or abrupt). For example via a Windows Job Object that ties child process lifetime to the parent, so the OS cleans up the whole tree even on a non-graceful exit.

Error Messages/Logs

Steps to Reproduce

  1. Configure at least one stdio-transport MCP server in Claude Code (a launcher script that spawns a server binary as a child process).
  2. Create a recurring scheduled task (via the scheduled-tasks feature) with a short interval, e.g. every 2 hours.
  3. Let it run unattended over several firings (hours/days) without interacting with it.
  4. After several cycles, inspect the OS process list (Task Manager / tasklist) for the MCP server binary's process name.
  5. Observed: one leftover instance per past scheduled run, none terminated - the count keeps growing with each new firing instead of staying constant.

Note: I have not specifically isolated whether ordinary interactive session closes are also affected - the evidence (orphan process creation timestamps lining up exactly with the cron schedule, 8x/day) points specifically at scheduled/background runs, but I can't rule out interactive closes contributing as well.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.237

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

PowerShell

Additional Information

Suspected mechanism (not confirmed, offered as a lead): a Windows stdio pipe handle-inheritance issue. The MCP server is invoked via a wrapper script, which spawns the actual server binary as a child while inheriting stdio handles from its own parent (the standard way stdio-transport MCP works). If the wrapper script itself retains a duplicate handle to that same pipe without explicitly closing it, then even after the true top-level parent exits and closes its own handle, the pipe's read side never sees EOF, because another process in the chain still holds a write-end handle open - so the server binary blocks forever waiting for input that will never arrive, and never notices its ultimate parent is gone.

Impact beyond the immediate resource leak: this is a silent, unbounded accumulation that isn't discoverable until a shared/limited downstream resource is exhausted - at which point the failure surfaces in a completely unrelated application, making the true cause hard to trace back to Claude Code at all.

Workarounds applied on our end (mitigate consequences, do not fix the underlying process-lifecycle issue): added an idle-timeout to our own database connection string, and added self-cleaning orphan-detection logic to our own MCP launcher script that runs at the start of each new launch and terminates any same-named process it finds with no living Claude Code ancestor.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗