Control Claude Code remotely via Telegram. A Python/FastAPI bridge that lets you interact with Claude Code from anywhere using your phone.
- Multi-session support -- Run Claude in different project directories simultaneously
- Permission handling -- Approve or deny Claude's permission requests via inline buttons
- Forum Topics -- Each conversation auto-creates a Telegram forum topic with a descriptive title
- Voice messages -- Send voice notes; transcribed locally with Whisper or via Voxtral API
- Photo analysis -- Send images directly to Claude for vision-based analysis
- Animated status -- Rotating "Thinking...", "Pondering..." etc. while Claude works
- Auto-continue -- Just reply naturally, no commands needed (10-minute window)
- Quick-reply buttons -- Tap numbered options directly
- Markdown rendering -- Claude's markdown converted to Telegram HTML
- Hook notifications -- Get notified in Telegram when local Claude finishes a task
- Three connection modes -- Polling (default), Tunnel, or Webhook
- Open Telegram and search for @BotFather
- Send
/newbotand follow the prompts - Choose a name (e.g., "My Claude Bot")
- Choose a username (must end in
bot, e.g.,my_claude_bot) - Save the bot token -- looks like
123456789:ABCdefGHIjklMNOpqrsTUVwxyz
- Start a chat with your new bot (search for it by username)
- Send any message to it (e.g., "hello")
- Open this URL in your browser (replace
YOUR_BOT_TOKEN):https://api.telegram.org/botYOUR_BOT_TOKEN/getUpdates - Find
"chat":{"id":in the response -- that number is your chat ID
The bot works best in a group with forum topics enabled — each conversation gets its own thread.
- Create a new Telegram group (or use an existing one)
- Go to Group Settings > Topics and enable Topics
- Add your bot to the group and make it admin (it needs permission to create/edit topics)
- Get the group chat ID (it will be negative, e.g.,
-1001234567890):- Send a message in the group
- Check
https://api.telegram.org/botYOUR_BOT_TOKEN/getUpdates - Use the
chat.idfrom the group message
- Use this group chat ID as your
TELEGRAM_CHAT_IDin.env
Note: The bot also works in direct messages (without topics), but you lose the threaded conversation history.
# Clone the repo
git clone https://github.com/FreakySurgeon/claude-telegram-bridge.git
cd claude-telegram-bridge
# Install dependencies
uv synccp .env.example .envEdit .env:
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_CHAT_ID=123456789
CLAUDE_CLI_PATH=claude
CLAUDE_WORKING_DIR=/path/to/your/project
MODE=pollinguv run uvicorn claude_telegram.main:app --host 0.0.0.0 --port 8000You should see:
Starting polling loop...
Polling started successfully
Application startup complete.
Now send a message to your bot!
No public URL needed -- polls Telegram's servers directly. Simple and reliable.
uv run uvicorn claude_telegram.main:appUses Cloudflare's free quick tunnels to create a public URL automatically. Lower latency but requires DNS propagation (2-5 minutes on first start).
# Install cloudflared first
# macOS: brew install cloudflared
# Linux: curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \
# -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared
MODE=tunnel uv run uvicorn claude_telegram.main:appUse your own public URL (e.g., behind nginx, Caddy, or a cloud provider).
MODE=webhook WEBHOOK_URL=https://your-domain.com uv run uvicorn claude_telegram.main:app| Command | Description |
|---|---|
/start, /help |
Show help with formatted commands |
/c <message> |
Continue previous session |
/new <message> |
Start fresh session (reset context) |
/dir <path> |
Switch to a different directory/session |
/dirs |
List all active sessions |
/repos |
Quick-switch to a favorite repo |
/rmdir <path> |
Remove a session from the list |
/compact |
Compact conversation context |
/cancel |
Cancel current running task |
/status |
Check if Claude is running |
<any text> |
Auto-continues if within 10 min, else new session |
Tips:
- Just type naturally -- conversations auto-continue for 10 minutes
- Quick replies like "1", "2", "yes", "no" always continue the current session
- Tap inline buttons for numbered options
Run Claude in different project directories simultaneously. Each directory maintains its own conversation context and history.
Important: Avoid using
/diron directories where you're actively running Claude locally. The bot and local CLI share the same session files (~/.claude/projects/), which can cause conflicts. Use the bot for directories you're not working on locally, or close your local Claude session first.
Add a new directory:
/dir ~/projects/backend
Switch between sessions: Use /dirs to see all sessions with numbered buttons:
You: /dirs
Bot: Active Sessions
> 1. frontend
2. api
[1. frontend] [2. api] <-- tap to switch
Example workflow:
You: /dir ~/projects/api
Bot: Switched to api
Status: idle
You: add input validation to the user endpoint
Bot: [api] Thinking...
Bot: I'll add validation to src/routes/user.ts...
You: /dir ~/projects/frontend
Bot: Switched to frontend
You: /dirs
Bot: Active Sessions
> 1. frontend
2. api
[1. frontend] [2. api]
You: *taps [2. api] button*
Bot: Switched to api
Status: idle, in conversation
Each session:
- Resumes from stored Claude sessions -- picks up where you left off via
~/.claude/projects/ - Shows previous context -- displays your last messages when switching to a stored session
- Has its own 10-minute auto-continue window
- Maintains separate conversation context
- Shows directory name in status messages (e.g.,
[api] Thinking...) - Quick-switch via numbered buttons
When used in a Telegram group with forum topics enabled, the bot automatically creates a new topic for each conversation. Topics are named with the date and a summary of the conversation:
[api] 15/02 - Add input validation to user endpoint
[frontend] 15/02 - Fix responsive layout on mobile
The topic title is generated from:
- A
<!-- title: ... -->comment in Claude's response (if Claude includes one) - A local LLM fallback (Ollama) for automatic summarization
- A truncated version of your first message as a last resort
This keeps your Telegram group organized with a clear history of all conversations.
Send a voice message to the bot and it will be transcribed automatically:
- Short messages (< 5 min): Transcribed locally using whisper.cpp -- fast, private, no API calls
- Long messages (>= 5 min): Transcribed via Mistral Voxtral API for better accuracy on longer audio
After transcription, the text is shown to you with a "Send to Claude" button for confirmation before processing.
Setup (optional):
# For local Whisper transcription
# 1. Build whisper.cpp: https://github.com/ggerganov/whisper.cpp
# 2. Download a model (e.g., medium)
# 3. Set in .env:
WHISPER_BIN=/opt/whisper.cpp/build/bin/whisper-cli
WHISPER_MODEL=/opt/whisper.cpp/models/ggml-medium.bin
# For Voxtral API (longer audio)
MISTRAL_API_KEY=your_mistral_keySend a photo to the bot and Claude will analyze it using vision capabilities. The image is downloaded and passed to Claude, which can describe contents, read text, analyze diagrams, review screenshots, and more.
When Claude needs to perform actions requiring permission (writing files, running commands, etc.), you get an interactive prompt:
You: create a file /tmp/hello.txt with hello world
Bot: Permission denied:
Write to /tmp/hello.txt
I need write permission for /tmp/hello.txt.
[Allow & Retry] [Deny]
- Allow & Retry -- Grants the specific tools that were denied and retries
- Deny -- Cancels the request
- Continue with bypass -- Retries with
--dangerously-skip-permissions(only shown when resuming a session that was originally started in bypass mode)
Supported permission types: Write, Edit, Read, Bash
Get notified in Telegram when Claude finishes a task in your local terminal. The included hook.py script reads the session summary and sends it to the bot.
Setup:
Add to your ~/.claude/settings.json:
{
"hooks": {
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "python /path/to/claude-telegram-bridge/hook.py completed",
"timeout": 30000
}]
}]
}
}When Claude finishes locally, the hook:
- Reads the latest session file from
~/.claude/projects/ - Extracts a summary of what Claude did (from the result or last assistant message)
- Sends a notification to your Telegram bot with the summary
Set HOOK_SERVER_URL in .env if the bot runs on a different host (defaults to http://localhost:8000).
The hook automatically skips notifications when Claude was triggered by the Telegram bot itself (to avoid duplicates).
# Build
docker build -t claude-telegram-bridge .
# Run with polling (default)
docker run -d \
-e TELEGRAM_BOT_TOKEN=your_token \
-e TELEGRAM_CHAT_ID=your_chat_id \
-e MODE=polling \
-v /usr/local/bin/claude:/usr/local/bin/claude:ro \
-v $(pwd):/workspace \
claude-telegram-bridge
# Or use docker-compose
docker compose up -d# Copy and edit the service file
sudo cp claude-telegram.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now claude-telegram
# Check status
sudo systemctl status claude-telegram
sudo journalctl -u claude-telegram -f| Variable | Default | Description |
|---|---|---|
TELEGRAM_BOT_TOKEN |
(required) | Bot token from @BotFather |
TELEGRAM_CHAT_ID |
(required) | Your chat ID (security: only this chat can use the bot) |
CLAUDE_CLI_PATH |
claude |
Path to Claude CLI binary |
CLAUDE_WORKING_DIR |
(none) | Default working directory for Claude |
MODE |
polling |
Connection mode: polling, tunnel, or webhook |
HOST |
0.0.0.0 |
Server bind host |
PORT |
8000 |
Server bind port |
WEBHOOK_URL |
(none) | Your public URL (webhook mode only) |
FAVORITE_REPOS |
(none) | Comma-separated paths relative to ~ for /repos |
MISTRAL_API_KEY |
(none) | Mistral API key for Voxtral transcription |
WHISPER_BIN |
/opt/whisper.cpp/build/bin/whisper-cli |
Path to whisper.cpp binary |
WHISPER_MODEL |
/opt/whisper.cpp/models/ggml-medium.bin |
Path to Whisper model |
HOOK_SERVER_URL |
http://localhost:8000 |
Bot server URL for hook notifications |
- Check the chat ID matches your
.env - Make sure you messaged the bot first (it cannot initiate conversations)
- Check server logs for errors
Claude is still processing a previous request. Use /cancel to stop it, or wait for it to finish.
This is normal for tunnel mode. Cloudflare quick tunnels take 2-5 minutes for DNS to propagate. The app retries automatically with exponential backoff. Just wait.
# Run with auto-reload
uv run uvicorn claude_telegram.main:app --reload
# Run tests
uv run pytest -v
# Run with coverage
uv run pytest --cov=claude_telegramclaude-telegram-bridge/
├── src/claude_telegram/
│ ├── main.py # FastAPI app, polling/webhook handlers, commands
│ ├── config.py # Pydantic settings from .env
│ ├── bots.py # BotConfig dataclass
│ ├── claude.py # Claude CLI runner, session management
│ ├── telegram.py # Telegram API client
│ ├── topic.py # Forum topic auto-naming
│ ├── transcribe.py # Whisper + Voxtral transcription
│ ├── markdown.py # Markdown to Telegram HTML
│ └── tunnel.py # Cloudflare tunnel manager
├── tests/ # Pytest suite
├── hook.py # Hook notification script
├── Dockerfile
├── docker-compose.yml
└── docker-entrypoint.sh
This project builds on the work of others:
- claude-code-telegram-bot by Amit Mor -- The original Telegram bot for Claude Code that inspired and provided the foundation for this project
- Claude Code Remote by Anthropic -- The reference implementation for remote Claude Code interaction
MIT -- see LICENSE for details.