Boris Cherny c4ce608cc2
telegram: don't kill a running session's poller when a second session starts
The startup takeover guard SIGTERMed any live bot.pid holder, so starting a
second Claude Code session stole the Telegram polling slot from the session
already using it — and when that second session exited, no poller remained
at all and the bot went permanently silent (anthropics/claude-code#81571).

Replace the kill-on-startup pattern with cooperative slot ownership:
- slot free (no pid file, holder dead, or pid recycled to an unrelated
  process — identity-checked via /proc cmdline or ps args) → claim and poll
- live healthy holder → standby: outbound tools stay fully usable, and a
  watcher claims the slot the moment the holder exits, so the channel hands
  off to the last session standing instead of dying
- nothing is ever killed; genuinely orphaned pollers are already reaped by
  the stdin orphan watchdog, and the pid file is removed by its owner on
  shutdown

Adds a regression test (bun test external_plugins/telegram/test) that spawns
two real servers against an isolated state dir: the second must not kill the
first, the slot must hand off when the holder exits, and the pid file must
be cleaned up by the last server out. Fails on 0.0.7, passes on 0.0.8.

Fixes anthropics/claude-code#81571

No-Verification-Needed: fix is in the plugins repo; verified by its own regression test plus a two-session CLI e2e
2026-08-24 18:06:02 -07:00
..

Telegram

Connect a Telegram bot to your Claude Code with an MCP server.

The MCP server logs into Telegram as a bot and provides tools to Claude to reply, react, or edit messages. When you message the bot, the server forwards the message to your Claude Code session.

Prerequisites

  • Bun — the MCP server runs on Bun. Install with curl -fsSL https://bun.sh/install | bash.

Quick Setup

Default pairing flow for a single-user DM bot. See ACCESS.md for groups and multi-user setups.

1. Create a bot with BotFather.

Open a chat with @BotFather on Telegram and send /newbot. BotFather asks for two things:

  • Name — the display name shown in chat headers (anything, can contain spaces)
  • Username — a unique handle ending in bot (e.g. my_assistant_bot). This becomes your bot's link: t.me/my_assistant_bot.

BotFather replies with a token that looks like 123456789:AAHfiqksKZ8... — that's the whole token, copy it including the leading number and colon.

2. Install the plugin.

These are Claude Code commands — run claude to start a session first.

Install the plugin:

/plugin install telegram@claude-plugins-official

3. Give the server the token.

/telegram:configure 123456789:AAHfiqksKZ8...

Writes TELEGRAM_BOT_TOKEN=... to ~/.claude/channels/telegram/.env. You can also write that file by hand, or set the variable in your shell environment — shell takes precedence.

To run multiple bots on one machine (different tokens, separate allowlists), point TELEGRAM_STATE_DIR at a different directory per instance.

4. Relaunch with the channel flag.

The server won't connect without this — exit your session and start a new one:

claude --channels plugin:telegram@claude-plugins-official

5. Pair.

With Claude Code running from the previous step, DM your bot on Telegram — it replies with a 6-character pairing code. If the bot doesn't respond, make sure your session is running with --channels. In your Claude Code session:

/telegram:access pair <code>

Your next DM reaches the assistant.

Unlike Discord, there's no server invite step — Telegram bots accept DMs immediately. Pairing handles the user-ID lookup so you never touch numeric IDs.

6. Lock it down.

Pairing is for capturing IDs. Once you're in, switch to allowlist so strangers don't get pairing-code replies. Ask Claude to do it, or /telegram:access policy allowlist directly.

Access control

See ACCESS.md for DM policies, groups, mention detection, delivery config, skill commands, and the access.json schema.

Quick reference: IDs are numeric user IDs (get yours from @userinfobot). Default policy is pairing. ackReaction only accepts Telegram's fixed emoji whitelist.

Tools exposed to the assistant

Tool Purpose
reply Send to a chat. Takes chat_id + text, optionally reply_to (message ID) for native threading and files (absolute paths) for attachments. Images (.jpg/.png/.gif/.webp) send as photos with inline preview; other types send as documents. Max 50MB each. Auto-chunks text; files send as separate messages after the text. Returns the sent message ID(s).
react Add an emoji reaction to a message by ID. Only Telegram's fixed whitelist is accepted (👍 👎🔥 👀 etc).
edit_message Edit a message the bot previously sent. Useful for "working…" → result progress updates. Only works on the bot's own messages.

Inbound messages trigger a typing indicator automatically — Telegram shows "botname is typing…" while the assistant works on a response.

Photos

Inbound photos are downloaded to ~/.claude/channels/telegram/inbox/ and the local path is included in the <channel> notification so the assistant can Read it. Telegram compresses photos — if you need the original file, send it as a document instead (long-press → Send as File).

Telegram's Bot API exposes neither message history nor search. The bot only sees messages as they arrive — no fetch_messages tool exists. If the assistant needs earlier context, it will ask you to paste or summarize.

This also means there's no download_attachment tool for historical messages — photos are downloaded eagerly on arrival since there's no way to fetch them later.