Rust drop-in replacement for telegram@claude-plugins-official -- the official Bun-based Telegram channel plugin for Claude Code.
3.5 MB static binary. ~5 MB RAM. <50 ms startup. Full feature parity. Zero migration needed.
The official plugin runs on Bun. Fine for one agent, breaks down at scale:
| Bun (official plugin) | Rust (this repo) | |
|---|---|---|
| RAM per instance | ~100 MB (JS runtime + dependencies) | ~5 MB |
| 10 parallel agents | ~1 GB just for Telegram bridges | ~50 MB |
| Startup time | 2-3s (bun install + boot) |
<50 ms |
| Zombie problem | Bot keeps polling after session ends, next session gets 409 Conflict | Immediate shutdown on stdin EOF |
| Dependencies | Bun + node_modules (~30 MB per plugin) | Single static binary |
If you run a fleet of Claude Code agents (CI workers, parallel dev agents, orchestrator setups), each agent spawns its own MCP subprocess. With Bun, 10 agents = 10 Bun runtimes. With Rust, 10 agents = 10 lightweight processes that start instantly and die cleanly.
The official plugin activation path has multiple open bugs in Claude Code:
- #36503 -- "Channels not available", inbound ignored
- #36477 -- Session stops processing after first response
- #38259 -- Telegram stops after completing a turn
- #38098 -- Plugin auto-spawns in all sessions
The workaround: register the server in .mcp.json and launch with --dangerously-load-development-channels server:telegram instead of --channels plugin:telegram@claude-plugins-official. This activates channel routing through a different code path that works reliably.
The easiest way to install is through our plugin marketplace:
/plugin marketplace add gohyperdev/hyperdev-claude-plugins
/plugin install hdcd-telegram@hyperdev-plugins
Then configure and launch:
/hdcd-telegram:configure <your-bot-token>
claude --channels plugin:hdcd-telegram@hyperdev-plugins
Note: The
--channelspath is currently gated by a server-side feature flag on Anthropic's side. If it doesn't work for your account, use the manual setup below with--dangerously-load-development-channelsas a workaround.
Option A -- Pre-built binary (recommended):
Download from GitHub Releases for your platform (Linux x86_64/ARM64, macOS Intel/Apple Silicon, Windows). Then verify:
shasum -a 256 -c SHA256SUMS.txtOn macOS, remove the quarantine flag:
chmod +x ./hdcd-telegram
xattr -d com.apple.quarantine ./hdcd-telegramOption B -- Build from source:
git clone https://github.com/gohyperdev/hdcd-telegram.git
cd hdcd-telegram
cargo build --release
# Binary: target/release/hdcd-telegramRequires Rust 1.80+. No native dependencies.
If you already have a bot token (e.g. from the official plugin at ~/.claude/channels/telegram/.env), skip to step 3.
- Open Telegram and message @BotFather
- Send
/newbot - Choose a display name (e.g. "My Claude Agent")
- Choose a username ending in
bot(e.g.my_claude_agent_bot) - BotFather replies with your bot token -- save it:
mkdir -p ~/.claude/channels/telegram
echo "TELEGRAM_BOT_TOKEN=123456789:AAH..." > ~/.claude/channels/telegram/.env
chmod 600 ~/.claude/channels/telegram/.envHow hdcd-telegram finds the token: On startup, the binary automatically reads
TELEGRAM_BOT_TOKENfrom~/.claude/channels/telegram/.env. This is the same location the official Telegram plugin uses — no extra wiring needed. If you need a custom location, set theTELEGRAM_STATE_DIRenvironment variable (see Configuration).
This step is critical if you want the bot to work in group chats. By default, Telegram bots in "privacy mode" only see messages that start with
/or explicitly @mention the bot. You must disable privacy mode so the bot can see all messages in the group.
- Message @BotFather → send
/setprivacy - Select your bot
- Choose Disable
BotFather confirms: "Privacy mode is disabled."
Order matters: If you already added the bot to a group before disabling privacy mode, the change won't take effect for that group. You must remove the bot from the group and re-add it after changing the privacy setting.
After adding the bot to a group:
- Promote the bot to admin (required for it to read all messages)
- Someone must send a message in the group for the bot to register the
chat_id
For 1:1 DMs (private chats), no extra configuration is needed -- the bot sees all messages by default.
Add to your project's .mcp.json (or ~/.claude.json for global):
{
"mcpServers": {
"telegram": {
"command": "/path/to/hdcd-telegram",
"args": []
}
}
}Replace /path/to/hdcd-telegram with the actual path to the binary (e.g. ./hdcd-telegram, /usr/local/bin/hdcd-telegram, or the full path to target/release/hdcd-telegram).
No token in
.mcp.json! You do NOT pass the bot token here. The binary reads it automatically from~/.claude/channels/telegram/.env(see step 2). The.mcp.jsononly tells Claude Code how to start the binary.If you want to pass the token explicitly (e.g. in CI), you can use the
envfield:{ "mcpServers": { "telegram": { "command": "/path/to/hdcd-telegram", "args": [], "env": { "TELEGRAM_BOT_TOKEN": "123456789:AAH..." } } } }
claude --dangerously-load-development-channels server:telegramWhy
--dangerously-load-development-channels? See Known issue above. This flag activates channel routing for servers registered in.mcp.json. It shows a one-time confirmation prompt.
Verify it works: On startup, Claude Code should show the telegram MCP server as connected. If you see --dangerously-load-development-channels ignored (server:telegram), check that:
- The server name in
.mcp.jsonis exactly"telegram"(must matchserver:telegram) - The
.mcp.jsonis in the directory you launchedclaudefrom, or in~/.claude.jsonfor global - The binary path in
"command"is correct and executable
DM your bot on Telegram. It replies with a 6-character pairing code. In your Claude Code session:
/telegram:access pair <code>
Done. Your next DM reaches Claude.
┌─────────────┐ ┌──────────────────┐ ┌────────────┐
│ Telegram │────▶│ hdcd-telegram │────▶│ Claude Code│
│ (user DM) │◀────│ (MCP server) │◀────│ (session) │
└─────────────┘ └──────────────────┘ └────────────┘
reads on startup:
~/.claude/channels/telegram/.env ← bot token
~/.claude/channels/telegram/access.json ← who's allowed
- Claude Code reads
.mcp.json, startshdcd-telegramas an MCP subprocess (stdio) - hdcd-telegram reads the bot token from
~/.claude/channels/telegram/.envand starts polling Telegram - Incoming Telegram message → hdcd-telegram sends
notifications/claude/channelvia JSON-RPC → Claude sees it as a<channel>tag - Claude replies via the
replyMCP tool → hdcd-telegram sends it back to Telegram
No ports opened. No webhooks. Everything runs locally over stdio + outbound HTTPS to api.telegram.org.
Channels only work with claude.ai OAuth authentication. API keys and Anthropic Console authentication do not work -- channel registration silently fails and inbound messages are never delivered.
If you're running in Docker, CI, or any headless environment, make sure you're logged in via the OAuth flow:
claude loginVerify your auth method:
claude config get authMethod
# Should return "oauth", not "apiKey"This is the most common hidden blocker -- if everything looks correct but messages don't arrive, check your auth method first.
This is a Claude Code issue, not a plugin issue. The --dangerously-load-development-channels flag should bypass this. If it doesn't:
- Check your auth method: must be claude.ai OAuth (see above).
- Check Claude Code version: run
claude --version. Versions before 2.1.90 may not support the flag correctly. - Check
.mcp.jsonis loaded: the telegram server must appear in the MCP server list at startup.
The plugin is receiving your message and forwarding it to Claude Code, but Claude Code isn't routing it into the conversation.
Most likely cause: wrong auth method. Channels require claude.ai OAuth -- if you're using an API key, the MCP server works (typing indicator) but channel notifications are silently dropped. See Authentication above.
If auth is correct, check:
- Are you using
--dangerously-load-development-channels server:telegram? (Not--channels plugin:...) - Is the startup output showing the channel as active (not "ignored")?
- Try restarting Claude Code -- some sessions lose channel routing after the first turn
Another process is polling the same bot token. This happens when:
- A previous Claude Code session didn't shut down cleanly
- You're running multiple instances with the same bot token
Fix: kill any leftover hdcd-telegram or bun processes, then restart.
pkill -f hdcd-telegram
pkill -f "bun.*telegram"See step 3 -- privacy mode must be disabled, and the bot must be re-added to the group after the change.
hdcd-telegram can transcribe voice messages using OpenAI Whisper locally. Transcription runs entirely on your machine -- no data leaves your network.
macOS:
pip install openai-whisper
brew install ffmpegLinux (Ubuntu/Debian):
pip install openai-whisper
sudo apt install ffmpegWindows:
pip install openai-whisper
choco install ffmpeg- Voice message arrives from Telegram
hdcd-telegramdownloads the.ogafile, converts to WAV viaffmpeg, runswhisper- Bot sends transcription back to chat: "Transcription: '...' -- reply 'ok' to confirm or correct the text"
- User confirms (
ok/yes/tak) or sends corrected text - Confirmed transcription is delivered to Claude as a
<channel>notification
If whisper or ffmpeg are not installed, voice messages are forwarded as "(voice message)" with the attachment_file_id in metadata -- Claude can still use the download_attachment tool to fetch the raw file.
| Env var | Default | Description |
|---|---|---|
TELEGRAM_BOT_TOKEN |
(required) | Bot token from @BotFather. Also read from ~/.claude/channels/telegram/.env. |
TELEGRAM_STATE_DIR |
~/.claude/channels/telegram |
State directory (access.json, inbox, pairing codes) |
WHISPER_MODEL |
small |
Whisper model size (tiny, base, small, medium, large) |
WHISPER_LANGUAGE |
auto-detect | Language hint (Polish, English, etc.) |
HDCD_ECHO_TRANSCRIPT |
true |
Send transcript back for user confirmation before delivering to Claude |
RUST_LOG |
hdcd_telegram=info |
Log level filter (tracing-subscriber format) |
- Your existing
~/.claude/channels/telegram/.envandaccess.jsonwork as-is - Replace the MCP entry in
.mcp.json(Bun command ->hdcd-telegrambinary path) - Launch with
--dangerously-load-development-channels server:telegraminstead of--channels plugin:telegram@claude-plugins-official - All pairings, group policies, and settings are preserved
- All 8 message types: text, photo, document, voice, audio, video, video note, sticker
- 4 MCP tools:
reply(with chunking, threading, file attachments, MarkdownV2),react,edit_message,download_attachment - Access control: pairing flow (6-hex code), allowlist, group policies with @mention gating
- Permission relay: inline keyboard for remote tool-use approval/denial (
claude/channel/permission) - Voice transcription (optional): automatic speech-to-text via whisper with echo-back confirmation flow
- Bot commands:
/start,/help,/status - 409 Conflict retry with exponential backoff
- Clean shutdown on stdin EOF (no zombie polling)
- Access control: 6-character hex pairing codes, per-user allowlists, group policies with @mention gating. Same model as the official plugin.
- Token handling: bot token stored in
~/.claude/channels/telegram/.env. The binary warns at startup if the file is world-readable (chmod 600recommended). - SecretToken: the
access.jsonallowlist uses Telegram user IDs, not usernames. Pairing codes are single-use and expire. - Sendable-file gate: prevents Claude from exfiltrating the plugin's own state directory via file attachments.
- No network listeners: communicates exclusively via stdio (JSON-RPC 2.0) and outbound HTTPS to
api.telegram.org. No ports opened.
Pre-built binaries are published on the GitHub Releases page for Linux (x86_64, ARM64), macOS (Intel, Apple Silicon), and Windows.
Every release includes a SHA256SUMS.txt file and per-archive .sha256 files. After downloading:
shasum -a 256 -c SHA256SUMS.txtThis confirms the file you downloaded matches what the CI pipeline produced. The checksums are generated in GitHub Actions, so they are only as trustworthy as the CI pipeline itself. For maximum assurance, build from source.
VirusTotal is a free service by Google that scans files against 70+ antivirus engines simultaneously. If you downloaded a binary and want to verify it is clean, upload it to virustotal.com before running it. We encourage this -- there is nothing to hide.
You can also check the SHA256 hash directly: go to virustotal.com, click "Search", and paste the SHA256 from SHA256SUMS.txt. If someone has already scanned that exact file, you will see the results without re-uploading.
macOS blocks unsigned binaries downloaded from the internet. After extracting, you will see:
"hdcd-telegram" can't be opened because Apple cannot check it for malicious software.
This happens because the binary is not signed with an Apple Developer ID certificate ($99/year). To allow it:
xattr -d com.apple.quarantine ./hdcd-telegramOr: System Settings > Privacy & Security > scroll down > "Allow Anyway".
Windows may show a "Windows protected your PC" warning for unsigned executables. Click "More info" > "Run anyway". This happens because the binary is not signed with an Authenticode certificate.
If you prefer not to trust pre-built binaries:
git clone https://github.com/gohyperdev/hdcd-telegram.git
cd hdcd-telegram
cargo build --release
# Binary: target/release/hdcd-telegramThis way you control the entire build chain. Requires Rust 1.80+.
| Level | Status | Description |
|---|---|---|
| SHA256 checksums | Done | Every release includes checksums |
| VirusTotal | Manual | We encourage users to verify on virustotal.com |
| Apple code signing + notarization | Planned | Eliminates macOS Gatekeeper warning |
| Windows Authenticode signing | Planned | Eliminates SmartScreen warning |
| GPG-signed releases | Planned | Cryptographic proof of publisher identity |
| Reproducible builds | Planned | Anyone can verify binary matches source |
- Claude Code by Anthropic -- the
claude/channelMCP capability this project builds on - OpenAI Whisper -- local speech-to-text for voice message transcription
- grammy -- the official TS plugin uses grammy; this Rust implementation talks to the same Telegram Bot API directly via reqwest
- tokio -- async runtime
- Model Context Protocol -- the underlying protocol for Claude Code extensions
Apache-2.0. See LICENSE.