Telegram plugin: dual-polling race condition when multiple Claude Code instances share one bot token
Description
When multiple Claude Code instances (e.g., Desktop app + terminal CLI) are running simultaneously with the Telegram plugin enabled, each instance spawns its own MCP subprocess that calls bot.start() (grammy long-polling). Telegram's getUpdates API delivers each update to only ONE active polling connection. This causes one instance to silently consume all inbound messages while the other instance receives nothing — even though outbound reply/react/edit_message tools work fine from both.
This is a distinct bug from the tengu_harbor feature flag issue reported in #36431 and #36503. Even when channels are properly enabled, dual-polling will cause message loss.
Environment
- Claude Code: 2.1.78 (Desktop) + terminal instance
- OS: macOS Darwin 25.3.0
- Plugin:
telegram@claude-plugins-officialv0.0.1 - Runtime: Bun
Steps to Reproduce
- Open Claude Desktop app (which loads the Telegram plugin and spawns
bun server.ts) - Open a terminal Claude Code instance (which also loads the plugin and spawns a second
bun server.ts) - Send a message to the bot on Telegram
- Observe: one instance receives the update, the other gets nothing
- The starved instance can still send via the
replytool, but never receives inbound messages
Observable Symptoms
- "Bun-2" visible in terminal when closing sessions — indicates a second bun process was spawned
- Outbound works perfectly from both instances
- Inbound silently fails on one instance with no error logged
typing...indicator may appear from the wrong instance's bot process- Persists across terminal restarts if the Desktop app (or an orphaned bun process) is still running
Root Cause
server.ts line 596 calls bot.start() unconditionally with no guard against concurrent pollers:
void bot.start({
onStart: info => {
botUsername = info.username
process.stderr.write(`telegram channel: polling as @${info.username}\n`)
},
})
Telegram's Bot API explicitly documents that only one getUpdates connection is allowed per bot token. A second connection either receives a 409 Conflict or silently loses the race for updates.
Proposed Fix
Add a lockfile (~/.claude/channels/telegram/bot.lock) that records the PID of the polling process. Before calling bot.start(), check the lock:
- If locked by a living process → skip
bot.start(), log a warning, but keep MCP tools functional - If locked by a dead process → take over the lock (stale lock cleanup)
- On exit → release the lock
This ensures only one poller runs at a time while allowing multiple instances to still use outbound tools.
A local patch implementing this fix has been tested and is available if helpful.
Related Issues
- #36431 — Telegram plugin: inbound MCP channel notifications not delivered to conversation
- #36503 —
--channelsplugin shows "Channels are not currently available" but inbound notifications are ignored
Those issues track the tengu_harbor feature flag gate. This issue is the plugin-level dual-polling race, which is an independent bug that will cause message loss even when the feature flag is enabled.
---
🐛 Discovered by Will Scheffer & Claude (Opus 4.6), 2026-03-20.
🤖 Generated with Claude Code
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗