[telegram plugin] Orphaned `bun server.ts` poller spins at 100% CPU indefinitely; self-termination guards all fail
Environment
- Plugin: telegram@claude-plugins-official v0.0.6
- Runtime: Bun, macOS (Darwin 25.5.0)
- Path: ~/.claude/plugins/cache/claude-plugins-official/telegram/0.0.6/server.ts
Summary
When the Claude Code session that launched the telegram MCP server dies uncleanly,
the poller becomes an orphan and pins one CPU core at ~100% indefinitely
(observed: 528 min accumulated on a single PID). It recurs across sessions.
Root cause (verified by inspection)
The process tree is TWO levels: a wrapper bun run --cwd .../telegram/0.0.6 start
spawns the actual bun server.ts. When the launching session dies, the WRAPPER is
reparented to PID 1 but stays alive — so the grandchild server.ts keeps an unchanged
PPID and a live stdin. This defeats all three self-termination guards:
- Orphan watchdog
process.ppid !== bootPpid— never trips (direct parent unchanged). - stdin
end/closehandlers — stdin still connected to the live wrapper. - PID-file reaper — sends SIGTERM only; the busy process never processes it.
Additionally, shutdown() relies on bot.stop() + setTimeout(exit, 2000), both of
which require event-loop progress. Because the process is CPU-bound, the timer never
fires, so even a delivered SIGTERM cannot terminate it.
NOTE: the existing 409-Conflict backoff (delay = min(1000*attempt, 15000), bail at 8)
is NOT the spin source — with backoff present the poller would idle, not burn CPU.
The actual busy-loop is something that fully blocks the event loop and was not
captured (process was killed before a stack sample). A sample <pid> from a live
occurrence would pinpoint it.
Impact
- Silent, permanent 100% CPU per orphan; multiple accumulate over days.
- Only reliable cleanup is manual
kill -9.
Suggested fixes
- Eliminate the wrapper layer (use
exec bun server.ts) so PPID/stdin death detection works again. - Reaper: escalate SIGTERM → SIGKILL after a short grace period.
- On orphan/duplicate detection, do a synchronous
process.exit()instead of a timer-based shutdown. - Watchdog: inspect the full ancestor chain to PID 1, not just direct PPID.
Workaround
Disable the plugin (enabledPlugins."telegram@claude-plugins-official": false) if unused.