Telegram plugin (0.0.6) inbound inject silently dropped — CC host doesn't declare experimental.claude/channel capability

Resolved 💬 2 comments Opened Jun 1, 2026 by gunijinifam-dev Closed Jun 9, 2026

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/telegram 0.0.6 (plugin name claude-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 rootsexperimental.claude/channel is absent.

Repro

  1. Install official telegram plugin 0.0.6, complete pairing.
  2. Confirm reply tool works (outbound healthy).
  3. Send a Telegram message from your phone to the bot.
  4. Observe: no <channel source="telegram"> appears in the active session.
  5. Tail debug.log (with optional debugLog instrumentation around mcp.notification): plugin logs handleInbound → 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/channel as 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.

View original on GitHub ↗

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