Telegram plugin (0.0.6) inbound inject silently dropped — CC host doesn't declare experimental.claude/channel capability
Summary
The official telegram plugin's inbound auto-inject (<channel source="telegram"> tag) never reaches the active Claude Code session. The plugin advertises itself as a server for the experimental capability claude/channel, but the CC CLI host does not declare that capability in its initialize response. Without a negotiated handler, the resulting notifications/claude/channel is silently dropped by the host (JSON-RPC notifications have no response, so neither side errors). Outbound (Claude → Telegram via reply tool) and polling are healthy; only the inbound auto-inject is broken.
Environment
- CC versions reproduced (5):
2.1.145,2.1.146,2.1.150,2.1.152,2.1.158 - Plugin:
claude-plugins-official/telegram0.0.6(plugin nameclaude-channel-telegram) - Runtime: macOS 25.3.0 (Darwin), Bun,
@modelcontextprotocol/sdk^1.0.0 - Bot: clean (BotFather) bot, single-user DM, allowlist passes (
gate=deliver)
Smoking Gun
After instrumenting server.ts with mcp.oninitialized = () => { debugLog('caps=' + JSON.stringify(mcp.getClientCapabilities())) }, killing the persistent bun MCP process, and reloading the plugin:
2026-06-01T04:06:46.138Z [DEBUG] CC client initialized:
name=claude-code ver=2.1.145
caps={"elicitation":{"form":{}},"roots":{}}
The plugin declares (server.ts:392-402, referencing internal anthropics/claude-cli-internal#23061):
experimental: {
'claude/channel': {},
'claude/channel/permission': {},
}
…but the host's client capabilities show only elicitation.form and roots — experimental.claude/channel is absent.
Repro
- Install official
telegramplugin 0.0.6, complete pairing. - Confirm
replytool works (outbound healthy). - Send a Telegram message from your phone to the bot.
- Observe: no
<channel source="telegram">appears in the active session. - Tail debug.log (with optional
debugLoginstrumentation aroundmcp.notification): plugin logshandleInbound → gate=deliver → calling mcp.notification → mcp.notification resolved OK(resolved OK = stdio bytes sent, not host-handled).
Hypotheses ruled out
| Hypothesis | Test | Result |
|---|---|---|
| Bot identity | swap to original BotFather bot (Test A, 2026-05-30) | ❌ identical failure |
| CLI version regression v2.1.146+ | downgrade to v2.1.145 (Test B, 2026-06-01) | ❌ identical failure |
| External daemon interference (Hermes gateway) | launchctl unload ai.hermes.gateway; kill python gateway | ❌ identical failure |
| Pairing / allowlist | gate=deliver confirmed in debug.log | ❌ not the cause |
| Env corruption | full clean reinstall via curl install.sh on 2026-05-30 | ❌ identical failure |
| MCP transport dead | reply tool, pending_update_count: 0, ESTABLISHED conn to 149.154.166.110 | ❌ transport healthy |
Asks
- Either ship
experimental.claude/channelas a publicly declared client capability in CC, or - Have the plugin rework around a publicly available mechanism (e.g., a tool the host explicitly polls for queued inbound).
Side issue worth flagging
The bun MCP plugin process is persistent across /reload-plugins. CC re-binds the plugin registry but does not respawn the plugin process unless the existing one is already dead. This affects anyone hot-patching plugin code: a patched server.ts is not loaded until the bun PID is killed manually. May want documented behavior or a flag to force a process restart.
Workaround in use
Channel can still be operated by explicit calls — user asks Claude to getUpdates / sendMessage manually. Only the auto-notification path is dead.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗