Telegram plugin leaks orphan bun server.ts processes across session restarts; stuck ones pin a full CPU core indefinitely
Summary
The claude-plugins-official/telegram plugin spawns a persistent bun server.ts process via bun run --cwd .../telegram/<version> --shell=bun --silent start. These processes are not cleaned up when Claude Code sessions end, when the plugin reloads within a session, or when the plugin version updates. They become orphans (reparent to launchd on macOS), accumulate indefinitely, and at least some of them eventually enter a hot loop pinning a full CPU core.
Environment
- macOS Darwin 25.3.0 (Apple Silicon, Mac Mini M4, 16 GB)
- Claude Desktop 1.569.0 (plugins via Claude Code)
- Plugin:
claude-plugins-official/telegramv0.0.4 (plus residual instances from oldexternal_plugins/telegrammarketplace path)
Observed state after ~10 days of normal use
12 concurrent bun server.ts processes for the telegram plugin were running simultaneously:
| Count | Origin | Age | CPU | Parent |
|---|---|---|---|---|
| 1 | external_plugins/telegram (old marketplace path) | 10 days | 98% | launchd (orphan) |
| 2 | cache/claude-plugins-official/telegram/0.0.4 | 4 days | ~100% each | launchd (orphan) |
| 1 | external_plugins/telegram | 10 days | 0% | launchd (orphan) |
| 8 | mix of both paths | 1-2 days | 0% | active claude process |
The three CPU-burning instances were the same plugin code that presumably worked fine when first spawned — they transitioned into a hot loop at some point with no visible trigger.
The 8 still-parented wrappers accumulated within a single claude-code session: every MCP reload spawned a new bun wrapper without killing the previous one. Same plugin version, same path, 9 processes stacked on top of each other.
Total: ~300% CPU burned by three stuck bun processes for days, causing:
- Fan noise and thermal pressure on an M4 Mac Mini that is otherwise silent under normal use
- Wasted electricity
- Subtle effect on other tools (other Node/bun processes throttled)
SIGTERM did not stop the 10-day-old hot-loop process; SIGKILL was required.
Expected behaviour
- Wrapper and its child
bun server.tsshould exit when the parent Claude Code session exits - On MCP reload within a session, the previous wrapper should be terminated before a new one starts
- On plugin version upgrade, the previous version's processes should be terminated
- No single plugin should ever have more than one
bun server.tsrunning per session - If a plugin enters an unresponsive / hot-loop state, it should be detected and restarted rather than left spinning forever
Actual behaviour
- No cleanup on session exit → orphans reparent to launchd and persist indefinitely
- No cleanup on MCP reload → wrappers stack
- No cleanup on plugin version upgrade → old path instances coexist with new path instances
- No watchdog on stuck state → hot-loop processes run forever at 100% CPU
- Additional observation: killing CPU-hog siblings apparently disconnected the active Telegram MCP in my session, suggesting some shared state or that the wrapper discovery logic is fragile
Reproduction (partial)
I don't have a reproducer for the hot-loop transition. The orphan / stacking half is trivial:
- Install
claude-plugins-official/telegramplugin - Use it in a Claude Code session
- Run
/mcpreload a few times pgrep -fl 'bun.*telegram'— observe multiple wrappers- Exit the Claude Code session
pgrep -fl 'bun.*telegram'— orphan wrappers persist
Workaround
Manual kill -9 and a daily cron that terminates bun server.ts processes older than N days from the claude-plugins path.
Suggested fixes
- Write a PID file on wrapper startup; kill stale instances before spawning a new one
- Propagate
SIGHUP/SIGTERMfrom the parent Claude process to the bun child on session exit - Track spawned MCP plugin PIDs in the Claude Code parent process and signal them on exit
- Add a CPU watchdog — if an MCP plugin exceeds a threshold for an extended period, restart it
- On plugin version upgrade, invoke the old version's cleanup before loading the new version
- Consider not spawning a long-running
bun server.tsat all if the MCP protocol doesn't require it
Happy to provide more logs or retry scenarios if helpful.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗