Bug: Telegram MCP Plugin - Orphaned Processes Cause CPU Exhaustion

Resolved 💬 3 comments Opened Apr 3, 2026 by BIG-TATHEP Closed Apr 7, 2026

Bug Report: Telegram MCP Plugin - Orphaned Processes Cause CPU Exhaustion

Summary

The Telegram MCP plugin spawns bun server.ts processes that do not terminate when Claude Code sessions end, leading to accumulation of orphaned processes that exhaust CPU resources.

Environment

  • Plugin: telegram@claude-plugins-official v0.0.4
  • OS: macOS Darwin 25.4.0
  • Runtime: Bun
  • Date: 2026-04-03

Problem Description

Observed Behavior

Over the course of approximately 10 hours of Claude Code usage, 27 instances of bun server.ts accumulated on the system:

$ ps aux | grep "bun server.ts" | grep -v grep | wc -l
27

$ ps aux | grep "bun server.ts" | grep -v grep | awk '{sum+=$3} END {print "Total CPU%:", sum}'
Total CPU%: 502.8

Each instance consumed approximately 18-22% CPU, totaling over 500% CPU usage and causing system-wide CPU exhaustion (99.9% CPU usage reported).

Timeline Evidence

PID     PPID   TTY      STARTED
34325   34320  ttys006  Fri Apr  3 09:42:35 2026  # Morning sessions
44369   44362  ttys007  Fri Apr  3 09:55:46 2026
78656   78653  ttys006  Fri Apr  3 10:31:24 2026
...
19822   19818  ??       Fri Apr  3 14:13:39 2026  # Orphaned (parent died)
26065   26058  ??       Fri Apr  3 14:26:39 2026  # Orphaned
...
75526   75524  ttys002  Fri Apr  3 19:12:36 2026  # Evening sessions

Orphaned Processes

Processes with TTY = ?? have lost their parent terminal session but continue running indefinitely. The parent PIDs (19818, 26058, etc.) no longer exist.

Working Directory

All processes originate from the Telegram plugin directory:

/Users/kobig/.claude/plugins/marketplaces/claude-plugins-official/external_plugins/telegram

Root Cause Analysis

Code Review

The server.ts implements graceful shutdown handlers (lines 619-632):

process.stdin.on('end', shutdown)
process.stdin.on('close', shutdown)
process.on('SIGTERM', shutdown)
process.on('SIGINT', shutdown)

However, these handlers are not triggered in the following scenarios:

  1. Terminal session closes abruptly — When a terminal tab/window is closed, processes may not receive SIGTERM
  2. Claude Code crashes — Unexpected exit doesn't always send SIGTERM to child processes
  3. System sleep/wake — Processes may be left in undefined state
  4. Parent process killed without signal propagationpkill on parent without -P flag

Missing Cleanup Mechanism

The plugin lacks:

  1. Pidfile management — No tracking of running instances
  2. Singleton enforcement — Multiple instances can run simultaneously
  3. Stale process detection — No check for zombie processes on startup
  4. Health check endpoint — No way to verify process liveness

Impact

  • CPU exhaustion — System becomes unresponsive
  • Battery drain — Continued CPU usage on laptops
  • Fan noise — Thermal management kicks in
  • Process table pollution — Accumulation over days/weeks

Suggested Fixes

1. Add Pidfile with Singleton Enforcement

const PID_FILE = join(STATE_DIR, 'server.pid')

function acquireLock(): void {
  const existingPid = readPidFile()
  if (existingPid && isProcessRunning(existingPid)) {
    process.stderr.write(`telegram channel: another instance is running (PID ${existingPid}), exiting\n`)
    process.exit(0)
  }
  writeFileSync(PID_FILE, String(process.pid))
}

function releaseLock(): void {
  try { rmSync(PID_FILE, { force: true }) } catch {}
}

function isProcessRunning(pid: number): boolean {
  try { process.kill(pid, 0); return true } catch { return false }
}

2. Add Stale Process Cleanup on Startup

function cleanupStaleProcesses(): void {
  // Check for orphaned bun server.ts processes from this plugin
  // Kill any process that:
  // 1. Matches the plugin's working directory
  // 2. Has a parent that no longer exists (PPID != current session)
}

3. Use Process Group for Clean Termination

// Spawn in a process group for easier cleanup
const server = spawn('bun', ['server.ts'], {
  detached: true,  // Create new process group
})
// On shutdown, kill entire group
process.kill(-server.pid, 'SIGTERM')

4. Add Heartbeat/Health Check

// Write heartbeat timestamp periodically
setInterval(() => {
  writeFileSync(join(STATE_DIR, 'heartbeat'), String(Date.now()))
}, 30000)

// On startup, check if last heartbeat is stale (> 5 min old)
// If stale, previous instance is likely dead

Workaround for Users

Until this is fixed, users can clean up orphaned processes with:

# Kill all orphaned telegram server processes
pgrep -f "bun server.ts" | xargs kill -9

# Or add to ~/.zshrc as an alias
alias clean-telegram='pgrep -f "bun server.ts" | xargs kill -9 2>/dev/null; echo "Cleaned up zombie processes"'

Severity

High — Causes significant performance degradation and can make systems unusable, especially for users who run Claude Code frequently throughout the day.

Steps to Reproduce

  1. Start Claude Code with Telegram channel: claude --channels plugin:telegram@claude-plugins-official
  2. Use Claude Code normally
  3. Close terminal tab or quit Claude Code
  4. Repeat steps 1-3 multiple times throughout the day
  5. Observe accumulated bun server.ts processes with ps aux | grep "bun server.ts"
  6. Monitor CPU usage climb to 100%

Additional Context

  • 409 Conflict handling (lines 959-994) correctly handles the case where a new instance tries to start while another is running, but doesn't address the orphaned process problem
  • The retry mechanism with backoff is good but doesn't help when processes become zombies

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗