Telegram plugin: inbound MCP channel notifications not delivered to conversation
Open 💬 20 comments Opened Mar 20, 2026 by gokhunguneyhan
💡 Likely answer: A maintainer (localden, collaborator)
responded on this thread — see the highlighted reply below.
Description
The Telegram plugin (telegram@claude-plugins-official v0.0.1) receives inbound messages correctly but they are never delivered to the active Claude Code conversation. Outbound messages (via the reply tool) work fine.
Environment
- Claude Code: 2.1.80
- OS: macOS (Darwin 24.6.0)
- Plugin version: 0.0.1 (commit
8908a582) - Runtime: Bun
Steps to Reproduce
- Install the Telegram plugin (
claude plugin install telegram) - Configure bot token and allowlist a user via
/telegram:access - Send a message to the bot on Telegram
- Observe that the bot shows "typing..." (confirming the message passes the gate and
handleInboundruns) - The
mcp.notification({ method: 'notifications/claude/channel', ... })call fires but nothing appears in the Claude Code conversation
What Works
- Bot token is valid (
getMereturns OK) - Bot polling is active (single process, no duplicates)
- Access gate passes (user is on allowlist, "typing..." indicator is sent)
- Outbound
replytool works — messages are delivered to Telegram - MCP server stdio pipes are connected (verified via
lsof)
What Doesn't Work
notifications/claude/channelsent by the plugin never surfaces in the conversation- This is reproducible across multiple restarts (tested 5+ times)
- Happens regardless of working directory (tested home dir and project dirs)
Root Cause Hypothesis
Claude Code's MCP notification handler does not route notifications/claude/channel events into the active conversation, even though the MCP server is properly connected and tool calls work bidirectionally.
Additional Context
- Initially worked briefly after first install, then broke after changing the working directory mid-session
- Killing stale MCP processes and restarting did not resolve the issue
/reload-pluginsspawns a fresh MCP server with valid stdio connections but inbound still fails
20 Comments
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Same issue here. Additional debugging details:
Environment: macOS Darwin 25.2.0, Claude Code 2.1.80, telegram@claude-plugins-official 0.0.1
What works:
"telegram@claude-plugins-official": true)bun server.ts), confirmed polling Telegram API vialsof(active TCP connections to 149.154.x.x)getMereturns OK)replytool works perfectly — messages delivered to TelegramgetUpdatesreturns empty — server is consuming inbound updatesallowFrom, policyallowlist)What doesn't work:
notifications/claude/channelnever surfaces in the conversation/reload-plugins, killing server + reload, full quit + restart, enabling remote-control in/configReproduction:
replytool — worksLooks like the MCP notification transport from server→Claude is silently dropping
notifications/claude/channelevents even though the stdio pipe is connected and tool calls work bidirectionally.Same issue here. macOS (Darwin 25.3.0), plugin v0.0.1.
Repro:
/telegram:access pair <code>handleInboundranreplytool (triggered by user showing me a screenshot) did the conversation start workingAdditional observation:
The root cause appears to be in
server.ts—handleInboundusesvoid mcp.notification(...)(fire-and-forget) with zero retry, no delivery confirmation, and no message queue. If the session isn't ready to receive at that exact moment, the message is silently lost.Suggesting the plugin should at minimum:
Same issue here. Adding debug evidence that narrows down the root cause further.
Environment: macOS Darwin 24.6.0,
--mcp-config(not plugin-enabled),--channels telegramSetup to eliminate confounders:
enabledPlugins: { "telegram@claude-plugins-official": false }— plugin disabled globallyclaude --mcp-config <mcp-telegram-config>.json --channels telegramps aux— no polling race conditionFile-level debug logging added to
server.ts:Added
fs.appendFileSyncat each stage inhandleInbound:Key finding:
mcp.notification({ method: 'notifications/claude/channel', params: {...} })resolves successfully (not just fire-and-forget — the Promise completes without error). The MCP stdio transport layer considers the notification delivered. Yet it never surfaces in the conversation.This confirms the issue is not in the plugin's notification sending logic, but in Claude Code's handling of
notifications/claude/channelon the receiving end. The stdio pipe is functional (outbound tool calls likereplywork fine from the same session), but inbound notifications are silently dropped.I solved this by starting claude with
--channels plugin:telegram@claude-plugins-officialSame issue here. Adding our debugging timeline in case it helps narrow things down.
Environment:
telegram@claude-plugins-officialv0.0.1What works:
bun runprocess)getMereturns OK)access.jsoncorrect: sender inallowFrom,dmPolicy: "pairing"replytool works — messages delivered to Telegram successfullygetWebhookInforeturns empty URL)What doesn't work:
getUpdatesreturnsok: truewith empty results (no 409 Conflict), suggesting polling may not be actively running despite the process being aliveDebugging performed across two sessions:
replytool — works perfectlygetUpdatesmanually — returnsok: truewithout 409, indicating the grammybot.start()async IIFE (line 826 ofserver.ts) may have failed silently or never started pollingserver.tsinitialization flow — thebot.start()runs inside avoid (async () => { ... })()block that swallows errors on non-409 failures (line 858: logs to stderr then returns)tengu_harborfeature gate on Claude Code's side — not a plugin issueAdditional observation:
In our case,
getUpdatesdoes NOT return 409 (which would confirm active polling). This might meanbot.start()silently failed during initialization — possibly because the MCP stdio transport isn't fully ready when the IIFE fires, or the feature gate rejection somehow propagates back and prevents polling from starting. Worth investigating if the channel registration rejection affects the bot's willingness to start polling.Waiting for the server-side flag rollout. Everything on the plugin side appears correctly configured.
Is this not how everyone who has this problem is starting it? That's certainly how I'm starting it. I wasn't aware there was any other way.
Claude actually directed me to start with just claude to activate tg. It spent all night trying to fix it internally before telling me to just comment '+1' in this issue and wait for fix. Eventually, I figured out the --channels fix myself. Good to know that's the standard way for you.
It's literally in the instructions. It's step 4.
Additional diagnostic data (macOS, Claude Code 2.1.81)
Setup:
claude --channels plugin:telegram@claude-plugins-official, Bun runtime, grammy 1.21+What works:
replytool → message delivered to Telegram ✅getUpdatesreturning 409 Conflict) ✅gate()returnsdeliverfor allowlisted senderId ✅handleInbound()executes fully,mcp.notification()called ✅What fails:
notifications/claude/channelnotification never appears in Claude Code conversation ❌Diagnostic steps taken:
ps aux | grep telegram→ 1 processgetWebhookInfo→url: ""getUpdates→ 409 Conflict (server IS polling)lsofon bot process: stdio unix sockets connected, but zero TCP connections at inspection time (transient HTTP?)access.json: senderId inallowFrom,pendingemptyConclusion: The MCP server correctly receives Telegram messages and calls
mcp.notification(), but the Claude Code client either doesn't register a handler fornotifications/claude/channelor silently drops the notification. The fire-and-forget pattern inhandleInbound()(.catch()only) means delivery failure is silent.Suggested fix: Add message queue with delivery confirmation / retry in the plugin, or fix the notification handler registration on the Claude Code client side.
Confirming this issue on Claude Code v2.1.91 (macOS, latest as of 2025-04-03).
Same symptoms as everyone else:
replytool sends messages to Telegram without issuesgetMesucceeds), no webhook set, singlebunprocess runninggetUpdatesreturns 409 Conflict when called externally, confirming the plugin holds the polling slot)lsofconfirms active HTTPS connections to149.154.166.110)getUpdatesreturns empty after user sends messages) butnotifications/claude/channelnever surfaces in the Claude Code conversationTried:
/reload-plugins, killing and restarting the MCP process,deleteWebhook, verifyingaccess.jsonallowlist matches user ID. None helped — the issue is clearly on the Claude Code client side not processingnotifications/claude/channelfrom MCP servers.Cross-linking #36837 — the Discord plugin shows the same
notifications/claude/channelsilent-drop pattern on the client side (it's one of three distinct bugs reported there, alongside Linux stdin lifecycle and discord.js cache staleness). If the macOS notification handler is a shared root cause, a fix likely lands both.Follow-up to my earlier cross-link, with a structural data point posted in detail on #36837. On a single Claude Code session (Kali Linux WSL2) with
tengu_harbor: truecached and the ledger entry for the official plugin present, theclaude-plugins-officialchannel plugins do not deliver inbound messages — while a community channel plugin (separate custom marketplace, same MCP-server +notifications/claude/channelmechanism) DOES deliver them end-to-end on the same session.This isolates the silent-drop pattern reported in this thread to the official-marketplace registration path rather than the notification handler itself — consistent with Gate 6 (plugin marketplace source verification) in @soumikbhatta''s #36460 breakdown. Telegram is in the same
claude-plugins-officialmarketplace as Discord, so this likely applies here too.The presence of
telegramintengu_harbor_ledgeris therefore not sufficient — something downstream of the ledger check rejectsclaude-plugins-officialplugins on affected accounts while custom-marketplace plugins go through.Correction to my earlier marketplace-isolation comment above. Tested end-to-end on my own Kali Linux WSL2 setup with
telegram@claude-plugins-officialv0.0.6, launched and paired normally — inbound DMs ARE delivered to the conversation and the assistant responds via thereplytool successfully.This invalidates the marketplace-isolation hypothesis I posted earlier, which extrapolated from the Discord case in #36837. The symptom reported in this thread appears to be fixed (or at least not reproducible) in the v0.0.6 release — most reports here are on v0.0.1. Upgrading the plugin may resolve it.
Apologies for the noise from the earlier inferred comment; keeping the thread honest.
Cross-posting workaround confirmation on the same setup, since @localden marked this as the consolidated tracker for
notifications/claude/channelsilent-drops (closing #36975 in favor of this issue):--dangerously-load-development-channels plugin:<id>works as a general workaround, but the severity differs by plugin on the same account:| Channel | --channels | --dangerously-load-development-channels |
|----------|------------------|------------------------------------------|
| Telegram | works | works |
| Slack | works | works |
| Discord | blocked silently | works |
(Telegram tested with
telegram@claude-plugins-officialv0.0.6.) For users on this thread still blocked on Telegram: two independent things to check — (a) upgrade to v0.0.6 if on the v0.0.1-era report (same symptom may already be cleared by upstream fixes), (b) failing that, the dev-flag workaround.The Discord side of the same bug is tracked in detail at #36837.
_(Edit: previous version of this comment had its table emojis mangled by a PowerShell stdin encoding issue; switched to plain text for the status column.)_
Still reproduces on v2.1.150 with
claude --bg— minimal deterministic reproConfirming
notifications/claude/channelis still never surfaced into the conversation for background daemon sessions on Claude Code v2.1.150. Here is a self-contained repro that doesn't depend on any real channel plugin, in case it helps narrow the root cause.Trivial stdio MCP server (
test-channel-plugin.js) that completes the handshake, then emits anotifications/claude/channel8s later asking the model to replyPONG:mcp-config.json:Run as a background daemon:
Observed (v2.1.150):
notifications/claude/channel(verified in its own stderr log).PONGand no trace of the channel content anywhere in the session./mcpreports1 MCP server failed.So the notification is accepted at the transport layer but never delivered into the conversation for
--bgsessions. The same trivial server delivers correctly in an interactive/tmux session — it is specifically the headless/--bgpath that drops it. This looks like the same root cause as #55896 (stream-json drop); flagging here since this is the consolidated tracker.Porting evidence from #64470 (closing as dup):
@kr-aldama captured
getClientCapabilities()from a connected MCP server: the result is{elicitation.form, roots}only —experimental.claude/channelis absent. This is direct confirmation that the host never negotiates the channel capability, which is whynotifications/claude/channelmessages are dropped.Also from #64470: still reproduces on plugin v0.0.6 + Claude Code 2.1.145–2.1.158 interactive (the "fixed in 0.0.6" comment above doesn't hold). References claude-cli-internal#23061.
Replying to @localden's note (comment):
For context, the "fixed in 0.0.6" suggestion came up in two places:
@sbenodiz's comment
("upgrade to v0.0.6 … same symptom may already be cleared by upstream fixes") and
my own follow-up
("appears to be fixed … in the v0.0.6 release"). Here's a data point that I think reconciles
both with the
getClientCapabilities()finding.Same plugin version across all three rows (telegram@claude-plugins-official v0.0.6) —
the only thing that changes is the Claude Code host version:
| Platform | Claude Code | Plugin | Inbound delivered? |
|---------------------|-------------|--------|--------------------|
| @localden's repro | 2.1.145–158 | v0.0.6 | ❌ no |
| Kali Linux (WSL2) | — | v0.0.6 | ✅ yes |
| Nobara (native) | 2.1.170 | v0.0.6 | ✅ yes |
Launched with
--channels plugin:telegram@claude-plugins-official. Inbound DMs surfacein the conversation (the
← telegram · <id>: <msg>line appears) and the assistantreplies via the
replytool, end-to-end.So both are right: the plugin version was never the differentiator (consistent with
"fixed in 0.0.6 doesn't hold"), but it does work for me — because the host is newer.
This points the fix at the Claude Code host, somewhere between 2.1.158 and 2.1.170,
exactly where the
getClientCapabilities()negotiation ofexperimental.claude/channelwould live.
Could anyone still blocked confirm their exact
claude --version? Would help pin thebuild where the capability negotiation got fixed.
for anyone needing to unblock phone control while this gets fixed, repowire takes a different route that sidesteps the
notifications/claude/channelpath completely.instead of a stdio channel plugin, telegram is a first-class peer registered on a local daemon mesh. inbound messages route through the daemon and get injected into the target session, outbound goes back the same way. since it never touches the channel capability negotiation thats failing here, both directions work today.
not a fix for the underlying client bug (@localden's capability-absence finding is the real root cause), but if you just need working telegram or slack control of claude code right now its a way around it. https://github.com/prassanna-ravishankar/repowire
Windows native confirmation — working on host 2.1.176
Adding a Windows data point to @DeliLevente99's table (was macOS + Linux only):
| Platform | Claude Code | Plugin | Inbound delivered? |
|---|---|---|---|
| Windows 11 (native, build 26200) | 2.1.176 | v0.0.6 | ✅ yes |
Launched with
--channels plugin:telegram@claude-plugins-official. Inbound DMs surface in the conversation and the assistant replies via thereplytool, end-to-end. MCP log shows"Channel notifications registered"(previously this same flag logged"Channel notifications skipped: server plugin:telegram:telegram not in --channels list").Consistent with the host-side fix landing between 2.1.158 and 2.1.170 — confirms it holds on native Windows too, not just macOS/Linux. Plugin v0.0.6 unchanged.