[plugin: telegram] server.ts spins at 100% CPU when multiple Claude Code sessions run concurrently — PID_FILE assumes single writer

Resolved 💬 3 comments Opened Jun 7, 2026 by wojciecholszewski Closed Jul 15, 2026

Summary

Plugin claude-plugins-official/telegram@0.0.6 leaks bun server.ts processes that pin a CPU core at 100% indefinitely. After 10 days uptime with multiple concurrent Claude Code sessions, I had 3 orphaned bun server.ts instances each consuming ~100% CPU, driving system load average to 84 and triggering macOS kernel_task thermal throttling.

Environment

  • Claude Code: 2.1.153
  • bun: 1.3.14
  • macOS Darwin 25.3.0 (Apple Silicon)
  • Plugin: claude-plugins-official/telegram 0.0.6

Root cause

server.ts tries to mitigate Telegram's "one getUpdates consumer per token" rule by writing its PID to ~/.claude/channels/telegram/bot.pid and SIGTERM-ing whatever PID it finds there at startup:

try {
  const stale = parseInt(readFileSync(PID_FILE, 'utf8'), 10)
  if (stale > 1 && stale !== process.pid) {
    process.kill(stale, 0)
    process.stderr.write(`telegram channel: replacing stale poller pid=${stale}\n`)
    process.kill(stale, 'SIGTERM')
  }
} catch {}
writeFileSync(PID_FILE, String(process.pid))

The file is a single slot. When N concurrent Claude Code sessions each spawn their own bun server.ts, the last writer wins the PID slot. The earlier processes:

  1. No longer have their PID recorded, so no future session will SIGTERM them.
  2. Keep calling bot.start() (long-polling getUpdates), which returns HTTP 409 Conflict because another instance now owns the slot.
  3. grammy's default retry behavior spins the request, pinning a CPU core at ~100%.

The .in_use/ directory under the plugin cache had 95 entries (one per session that ever loaded the plugin in 10 days) while only the most recent session's PID was tracked.

Reproduction

  1. Open ≥2 concurrent Claude Code sessions that both load the telegram plugin
  2. Wait for both bun server.ts instances to start
  3. Observe: the older one pins a CPU core at 100% on getUpdates 409 retries
  4. Verify with ps -Ao pid,pcpu,args | grep "bun.*server\.ts"

Suggested fixes

Pick one (in order of robustness):

  • flock/exclusive lock on bot.pid — second instance detects the lock and exits cleanly with a clear message. Only one poller per host ever runs.
  • Detect 409 Conflict in the getUpdates handler and process.exit(0) — current instance becomes orphan-safe; no longer relies on the next session's startup logic as the only safety net.
  • Append-only PID list + SIGTERM all stale on startup — keeps current architecture, just fixes the single-slot bug.

Workaround

pkill -9 -f "bun.*server\.ts"

Next /telegram invocation respawns one cleanly.

View original on GitHub ↗

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