Channel MCP plugin: orphan process after server restart causes silent message loss
Summary
When a channel MCP server (e.g., the Telegram plugin) dies mid-session, Claude Code respawns it but the new process's stdio is not connected to the MCP transport. The respawned process continues operating (e.g., polling Telegram API via getUpdates) but notifications/claude/channel never reaches Claude. Messages are silently consumed and lost with no error indicator — the status bar still shows the MCP as connected.
Steps to Reproduce
- Start Claude Code with
--channels plugin:telegram@claude-plugins-official - Send a Telegram message — it arrives normally (
← telegram · ...) - Kill the bun child process:
kill $(pgrep -P <claude_pid> -f bun) - Claude Code respawns bun automatically
- Send another Telegram message — it never appears in Claude
Observed Behavior
- After step 4,
pgrep -f "bun.*telegram"shows a new bun process - But
ps --ppid <claude_pid> --no-headers | grep bunreturns nothing — the new bun is not a child of Claude (reparented, likely spawned through a shell) - The new bun's grammy long-polling works correctly (consumes
getUpdates), but its stdout socket is not connected to Claude's MCP message reader mcp.notification({ method: 'notifications/claude/channel', ... })writes JSON-RPC to stdout → nobody reads it → message lost- Telegram's update offset advances → message cannot be re-fetched
- Claude's status bar still shows the MCP server as connected (e.g.,
8 MCPs)
Expected Behavior
Either:
- The respawned MCP server's stdio should be correctly piped back to Claude's MCP transport handler, OR
- If reconnection isn't possible, Claude should detect the broken pipe and report the MCP as disconnected (removing tools from the available set), rather than showing a stale "connected" state
Impact
This bug is particularly severe for channel plugins with inbound notifications (currently only Telegram). Unlike regular MCP tool servers where a broken connection surfaces as a tool call error, channel plugins push messages to Claude via fire-and-forget notifications. When the notification path breaks:
- No error is surfaced to Claude or the user
- The orphan process consumes messages from the external service (advancing offsets/cursors)
- Messages are permanently lost with no retry mechanism
Workaround
External watchdog that checks if the bun process is a child of Claude, and restarts the full service if not:
PANE_PID=$(tmux display-message -t claude-telegram -p '#{pane_pid}')
BUN_PID=$(pgrep -f "bun.*telegram" | head -1)
IS_CHILD=$(ps --ppid "$PANE_PID" --no-headers | grep -c bun)
if [[ -n "$BUN_PID" && $IS_CHILD -eq 0 ]]; then
kill "$BUN_PID"
# restart the full Claude Code session
fi
Environment
- Claude Code: 2.1.101
- Telegram plugin: 0.0.5
- OS: Ubuntu 24.04 (WSL2)
- MCP SDK:
@modelcontextprotocol/sdk(StdioServerTransport)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗