[BUG] Telegram plugin's MCP server hijacked by other Claude Code sessions, ignoring `--channels`

Resolved 💬 3 comments Opened Apr 29, 2026 by jacques-kruger-za Closed May 31, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When the Telegram plugin is installed at user scope, every Claude Code session that loads the plugin claims the Telegram bot's long-poll lock — regardless of whether that session was launched with --channels plugin:telegram@....

Concretely:

  1. The plugin's MCP server (server.ts) is a long-running bun subprocess started whenever the plugin loads.
  2. On startup, it reads ~/.claude/channels/telegram/bot.pid, sends the previous PID SIGTERM, writes its own PID, and starts bot.start() (long-poll). This is unconditional — there is no flag, env var, or check that gates it.
  3. Telegram's Bot API permits exactly one getUpdates consumer per token. The plugin's "newest wins" takeover means that any secondary Claude Code session — claude -p from a script, a cron job, an IDE invocation, a parallel interactive session, a subprocess spawned by user code — silently kills the listener session's bun.
  4. The listener session sees a generic MCP disconnect with no cause given. Inbound Telegram messages stop arriving. Recovery requires manual /mcp reconnect.
  5. The --channels plugin:telegram@... flag — which a reasonable user assumes is what designates "this is the listener" — has no effect on the plugin's takeover behavior. It only controls whether the parent Claude Code process delivers inbound messages into its prompt; the flag is never propagated to the bun subprocess.

Net effect: on a single-user machine running a persistent Hobbes/Telegram-listener session and any other Claude Code activity, Telegram inbound delivery breaks silently and repeatedly throughout the day. We hit this 4+ times in 11 hours.for me into

What Should Happen?

The intuitive contract of --channels plugin:X is "this session is the channel listener for plugin X." That should be enforced end-to-end:

  1. Only the session designated by --channels should hold the long-poll lock. Other sessions with the plugin loaded should expose the plugin's tools (so they can reply, react, send_chat_action programmatically) but must not claim bot.pid or call bot.start().
  2. --channels should propagate to the plugin subprocess. Today the bun has no way to know whether its parent claude was launched as a listener or a tool-only consumer. The harness should pass that signal through (e.g. an env var TELEGRAM_CHANNEL_ACTIVE=1 set when --channels plugin:telegram@... is in argv).
  3. The plugin should default to tools-only. The takeover routine and bot.start() should be gated on the env var above. Default = tools available, no polling. Listener mode = explicit opt-in via the env var the harness sets from --channels.
  4. **When the listener is evicted (legitimately, e.g. user manually intervenes), the harness should surface a clear message** in the listener session, not a bare "MCP disconnected." Today the user has to read the plugin's per-launch log file in ~/.cache/claude-cli-nodejs/.../mcp-logs-plugin-telegram-telegram/ to find out their listener was hijacked.

The combination of (1)-(3) is a small plugin patch plus a small harness change. The combination would make --channels mean what users already expect it to mean.

Error Messages/Logs

Steps to Reproduce

Environment: Ubuntu 24.04, Claude Code latest, telegram plugin v0.0.6.

  1. Install and enable the telegram plugin globally (user scope) with a valid bot token at ~/.claude/channels/telegram/.env.
  2. Launch session A (the intended listener):

``
claude --channels plugin:telegram@claude-plugins-official
`
Confirm it can react/reply to Telegram messages. Note its bun PID:
`
pgrep -f "bun.*plugins/cache/.*telegram"
``

  1. From a separate terminal, launch session B without --channels in any other directory:

``
claude -p "echo hi" --model sonnet
`
Even though session B has no
--channels` flag, it loads the telegram plugin, spawns its own bun, and that bun calls the "replacing stale poller" routine that SIGTERMs session A's bun.

  1. Result: session A's MCP connection silently drops. Inbound Telegram messages no longer arrive in session A's prompt. From the user's perspective, "Telegram is broken" with no indication why.
  2. Worse: when session B exits (e.g., claude -p finishes), its bun exits too. The poller slot is now empty — Telegram is fully dark until the user manually /mcp reconnects in session A or restarts the session.

This reproduces 100% of the time. Any secondary claude that loads the plugin — including ones spawned as subprocess by user code (we hit this with a Python wrapper that calls claude -p for analysis tasks), cron-spawned claude -p runs, IDE claude invocations, and --bare-less --print calls — will sever the listener session.

Claude Model

Not sure / Multiple models

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.123 (Claude Code)

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

Other

Additional Information

_No response_

View original on GitHub ↗

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