Telegram Channels: inbound messages never reach the session (outbound works)
Summary
With the Telegram channel configured and paired correctly, outbound messages work (the reply MCP tool successfully delivers to Telegram), but inbound messages from Telegram never reach the session as a <channel source="telegram"> turn — even though the plugin's own server.ts confirms the sender is allowlisted and calls mcp.notification({method: 'notifications/claude/channel', ...}) with the message content.
Environment
- Claude Code: 2.1.209, running via the macOS desktop app (
Claude.app) - macOS: 15.5 (24F74)
- Telegram plugin: directory version
0.0.6(claude-plugins-official/telegram/0.0.6), butpackage.jsoninside reports"version": "0.0.1"— noting the mismatch in case it's relevant - Bun: 1.3.14
- Plugin confirmed already at the latest available version via
/plugin update
Steps to reproduce
- Install the Telegram plugin:
/plugin marketplace add anthropics/claude-plugins-official,/plugin install telegram@claude-plugins-official,/reload-plugins. - Configure a bot token via
/telegram:configure. - Ensure the session is running with the channel active (in this case the plugin was auto-loaded by the desktop app rather than via an explicit
--channelsCLI flag). - Pair: DM the bot from Telegram, receive a 6-character pairing code, approve it with
/telegram:access pair <code>. - DM the bot again with a plain text message (e.g. "hi").
Expected
The message should surface in the session as documented in the plugin's own MCP server instructions:
"Messages from Telegram arrive as <channel source="telegram" chat_id="..." message_id="..." user="..." ts="...">"
...allowing a reply via the reply tool.
Actual
Telegram shows a "typing…" indicator (confirming the bot process received the message and gate() ran), but no message ever appears in the session. This was reproduced consistently across multiple send attempts and a full pairing reset.
Diagnosis already ruled out
Before filing, we went through the following to isolate the cause (happy to share the full transcript if useful):
- Duplicate poller: found and killed a second
bun server.tsprocess from a separate, unrelated session that was also holding the same bot token (Telegram'sgetUpdatesonly allows one consumer). Confirmed viaps/lsofthat only one poller remained afterward, holding the liveESTABLISHEDconnection to Telegram's API (149.154.166.110:https). - Access config: confirmed
~/.claude/channels/telegram/access.jsoncorrectly has the sender's ID inallowFromafter pairing. - Server source review: read
server.tsdirectly.gate()returns{ action: 'deliver', access }for an allowlisted DM sender as expected.handleInbound()then callsmcp.notification({ method: 'notifications/claude/channel', params: { content: text, meta: {...} } })— this is the mechanism that should become a visible turn. - Outbound sanity check: called the
replyMCP tool directly with the knownchat_idfromaccess.json(no inbound message required, since Telegram DMchat_id == user_id). It succeeded and the message was received in Telegram — confirming the MCP tool-call direction and the bot's Telegram API connection both work fine. - Full pairing reset: cleared
access.jsonback to a freshpairingstate, had the sender re-DM the bot for a new code, re-ran/telegram:access pair <code>. Same result — outbound works, inbound never surfaces. - Plugin already latest:
/plugin update telegram@claude-plugins-officialreported no update available.
Given the plugin declares 'claude/channel': {} as an experimental MCP capability, and everything on the plugin's own side of the pipeline (gating, notification dispatch) checks out, this looks like a gap in how this specific harness build (2.1.209, desktop app) receives/surfaces that experimental notification type — not a config or plugin-version issue.
Additional notes
- The Telegram plugin was auto-loaded into this session by the desktop app without an explicit
--channelsCLI invocation, which may be relevant if--channelstriggers additional wiring that auto-load doesn't. server.tshas its own stale-poller self-healing (kills a PID recorded inbot.pidon startup if still alive) — worth knowing this exists if others hit the duplicate-poller symptom first.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗