Bidirectional communication between Claude Code CLI and Telegram. Control your Claude Code sessions from your phone.
Supported platforms: Linux x64, Linux arm64, macOS ARM64, macOS Intel x64
npm install -g claude-telegram-mirror
ctm setup # Interactive setup wizardThis installs a native Rust binary (ctm) via platform-specific optional packages. No Node.js runtime is needed to run the binary itself β Node.js 18+ is only required as the npm distribution mechanism.
- CLI to Telegram: Mirror Claude's responses, tool usage, and notifications
- Telegram to CLI: Send prompts from Telegram directly to Claude Code
- Tool Summarizer: Human-readable summaries for 30+ command patterns ("Running tests" instead of "Running: Bash")
- AskUserQuestion Rendering: Inline buttons for Claude's interactive questions
- Photo & Document Upload: Send images/files from Telegram, path injected into Claude
- Stop/Interrupt: Type
stopto send Escape,killto send Ctrl-C - Session Threading: Each Claude session gets its own Forum Topic
- Session Rename:
/renamesyncs with Claude Code's session title - Multi-System Support: Run separate daemons on multiple machines
- Compaction Notifications: Get notified when Claude summarizes context
- Governor Rate Limiting: MessageQueue with retry and exponential backoff
- Doctor Auto-Fix:
ctm doctor --fixauto-remediates common issues - Token Scrubbing: Global regex-based scrubbing prevents bot tokens from leaking to logs
- Atomic PID Locking:
flock(2)prevents duplicate daemon instances - Path Traversal Protection: Transcript paths validated and canonicalized before file access
- Char-Boundary Safe: Unicode-safe message chunking and string truncation throughout
# 1. Install globally
npm install -g claude-telegram-mirror
# 2. Run interactive setup (creates bot, configures everything)
ctm setup
# 3. Start the daemon
ctm start
# 4. Run Claude in tmux
tmux new -s claude
claude# Setup & diagnostics
ctm setup # Interactive setup wizard
ctm doctor # Diagnose configuration issues
ctm doctor --fix # Auto-fix detected issues
# Daemon control
ctm start # Start daemon (foreground mode)
ctm stop # Stop running daemon
ctm stop --force # Force stop if graceful shutdown fails
ctm restart # Restart daemon
ctm status # Show daemon status, config, and hooks
ctm config --test # Test Telegram connection
ctm toggle # Toggle mirroring on/off
ctm toggle --on # Force mirroring ON
ctm toggle --off # Force mirroring OFF
# Hook management
ctm install-hooks # Install global hooks
ctm install-hooks -p # Install to current project's .claude/
ctm uninstall-hooks # Remove hooks
ctm hooks # Show hook status
# OS service management (optional, for auto-start on boot)
ctm service install # Install as systemd/launchd service
ctm service uninstall # Remove system service
ctm service start # Start via service manager
ctm service stop # Stop via service manager
ctm service restart # Restart via service manager
ctm service status # Show service statusNote: ctm stop and ctm restart auto-detect whether the daemon is running directly or via a system service and use the appropriate method.
| Command | Action |
|---|---|
| Any text | Sends to Claude as input |
stop |
Sends Escape to pause Claude |
kill |
Sends Ctrl-C to exit Claude entirely |
cc <cmd> |
Sends /<cmd> as a slash command to Claude |
/status |
Show active sessions and mirroring state |
/sessions |
List active sessions with age and project dir |
/rename <name> |
Rename session (syncs with Claude Code) |
/attach <id> |
Attach to a session for updates |
/detach |
Detach from current session |
/mute / /unmute |
Suppress/resume agent response notifications |
/toggle |
Toggle mirroring on/off |
/abort |
Abort the attached session |
/ping |
Measure round-trip latency |
/help |
Show all commands |
See docs/ARCHITECTURE.md for additional details and command aliases.
When Claude requests to use a tool that requires permission (Write, Edit, Bash with non-safe commands), you'll see approval buttons in Telegram:
| Button | Action |
|---|---|
| Approve | Allow the tool to execute |
| Reject | Deny this specific tool execution |
| Abort | Stop the entire Claude session |
| Details | View full tool input parameters |
Approval buttons only appear in normal mode, not with --dangerously-skip-permissions. If you don't respond within 5 minutes, Claude falls back to CLI approval.
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Claude Code ββββββΆβ ctm daemon ββββββΆβ Telegram β
β CLI βββββββ (Rust binary) βββββββ Bot β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β β
β hooks β Unix socket
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β ctm hook ββββββΆβ Socket Server β
β (same binary, βββββββ (bidirectional)β
β hook mode) β β β
βββββββββββββββββββ βββββββββββββββββββ
Flow:
- Claude Code hooks invoke
ctm hook, which reads the event from stdin - PreToolUse: sends approval request via socket, waits for Telegram response
- Other hooks: sends JSON to daemon via socket and exits immediately
- Daemon forwards messages to Telegram Forum Topic with summarized tool actions
- Telegram replies are injected into CLI via
tmux send-keys - Stop/kill commands send Escape or Ctrl-C to interrupt Claude
When running Claude Code on multiple machines, each system needs its own bot to avoid Telegram API conflicts (error 409: only one polling connection per bot token is allowed).
The model:
- One daemon per host - Each machine runs its own bridge daemon
- One bot per daemon - Each daemon uses a unique Telegram bot
- Multiple sessions per host - One daemon handles all Claude sessions on that machine
- Shared supergroup - All bots post to the same Telegram supergroup
- Create one bot per system via @BotFather
- Add all bots to the same supergroup with admin permissions
- Configure each system with its own bot token:
# On System A (~/.telegram-env) export TELEGRAM_BOT_TOKEN="token-for-system-a-bot" export TELEGRAM_CHAT_ID="-100shared-group-id" # On System B (~/.telegram-env) export TELEGRAM_BOT_TOKEN="token-for-system-b-bot" export TELEGRAM_CHAT_ID="-100shared-group-id" # Same group!
- Each daemon creates topics for its sessions - Messages route correctly because each daemon only processes topics it created.
- Node.js 18+ (for npm installation only)
- Claude Code CLI
- tmux (for bidirectional communication)
- Telegram account
- Message @BotFather ->
/newbot - Choose name and username (must end in
bot) - Save the API token
- Create a new group in Telegram
- Add your bot to the group
- Group Settings -> Enable Topics
- Group Settings -> Administrators -> Add your bot
- Enable: Manage Topics, Post Messages
- Send any message in the group
- Visit
https://api.telegram.org/botYOUR_TOKEN/getUpdates - Copy the chat ID (supergroups start with
-100)
- @BotFather ->
/mybots-> Select bot - Bot Settings -> Group Privacy -> Turn off
Create ~/.telegram-env:
export TELEGRAM_BOT_TOKEN="123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
export TELEGRAM_CHAT_ID="-1001234567890"
export TELEGRAM_MIRROR=true
# Optional:
# export TELEGRAM_MIRROR_VERBOSE=true
# export TELEGRAM_BRIDGE_SOCKET=~/.config/claude-telegram-mirror/bridge.sock
# export TELEGRAM_STALE_SESSION_TIMEOUT_HOURS=72 # Auto-cleanup dead sessions (default: 72)Source in your shell profile (~/.bashrc or ~/.zshrc):
[[ -f ~/.telegram-env ]] && source ~/.telegram-envThe ctm setup wizard creates ~/.config/claude-telegram-mirror/config.json:
{
"botToken": "your-token",
"chatId": -1001234567890,
"enabled": true,
"verbose": true
}Environment variables take precedence over config file values.
ctm doctor
# Checks: config, hooks, socket, tmux, systemd/launchd, Telegram APIIf your project has .claude/settings.json with custom hooks, global hooks are ignored. Install hooks to the project:
cd /path/to/your/project
ctm install-hooks --project| Direction | Event | Display |
|---|---|---|
| CLI -> Telegram | User types | User (cli): ... |
| CLI -> Telegram | Tool starts | Running tests (summarized) |
| CLI -> Telegram | Claude responds | Claude: ... |
| CLI -> Telegram | Session starts | New Forum Topic created |
| CLI -> Telegram | Context compacting | Notification sent |
| CLI -> Telegram | AskUserQuestion | Inline buttons rendered |
| Telegram -> CLI | User sends message | Injected via tmux |
| Telegram -> CLI | User sends photo | Downloaded, path injected |
| Telegram -> CLI | User types "stop" | Sends Escape interrupt |
- Binary: Single native Rust executable (
ctm), ~10 MB - Session storage: SQLite at
~/.config/claude-telegram-mirror/sessions.db - Socket path:
~/.config/claude-telegram-mirror/bridge.sock - PID file:
~/.config/claude-telegram-mirror/bridge.pid(flock-guarded) - Downloads:
~/.config/claude-telegram-mirror/downloads/(0700 permissions) - Response extraction: Reads Claude's transcript
.jsonlon Stop event - Deduplication: Telegram-originated messages tracked to prevent echo
- Topic routing: Each daemon only processes topics it created (multi-bot safe)
- Rate limiting: Governor-based with exponential backoff retry queue
- Token scrubbing: All log output filtered through regex to strip bot tokens
- Test suite: 512 Rust tests (unit + 10 integration test files)
Run the diagnostic tool first:
ctm doctor
ctm doctor --fix # Auto-fix common issuesHooks not firing?
- Check if project has local
.claude/settings.jsonoverriding globals - Run
ctm install-hooks -pfrom project directory - Restart Claude Code after installing hooks
409 Conflict error?
- Only one polling connection per bot token is allowed
- If running multiple systems, each needs its own bot (see Multi-System Architecture)
- Kill duplicate daemons:
ctm stop --force
Bridge not receiving events?
- Check socket:
ls -la ~/.config/claude-telegram-mirror/bridge.sock - Check daemon logs for errors
- Run
ctm statusto verify daemon is running
tmux injection not working?
- Verify tmux session:
tmux list-sessions - Check daemon logs for "Session tmux target stored"
Messages going to wrong topic?
- Clear session DB:
rm ~/.config/claude-telegram-mirror/sessions.db
Service not starting (Linux)?
- Check status:
systemctl --user status claude-telegram-mirror - View logs:
journalctl --user -u claude-telegram-mirror -f - Enable linger:
loginctl enable-linger $USER
Service not starting (macOS)?
- Check status:
launchctl list | grep claude - View logs:
cat ~/Library/Logs/claude-telegram-mirror.*.log
Click to expand
For developers who want to build from source or contribute:
# 1. Clone and build
git clone https://github.com/robertelee78/claude-telegram-mirror.git
cd claude-telegram-mirror/rust-crates
cargo build --release
# Binary at: rust-crates/target/release/ctm
# 2. Run tests
cargo test
# 3. Use the binary directly
./target/release/ctm setup
./target/release/ctm startrust-crates/ctm/src/
main.rs # CLI entry point (clap)
lib.rs # Library re-exports
hook.rs # Hook event processing
config.rs # Configuration loading (env > file > defaults)
error.rs # Centralized error types (thiserror)
types.rs # Shared types, validation, security constants
session.rs # SQLite session management
socket.rs # Unix socket server/client (flock, NDJSON)
injector.rs # tmux input injection
formatting.rs # Message formatting, chunking, ANSI stripping
summarize.rs # Tool action summarizer (30+ patterns)
colors.rs # ANSI color helpers for terminal output
doctor.rs # Diagnostic checks with --fix
installer.rs # Hook installer
setup.rs # Interactive setup wizard
bot/ # Telegram API client (client.rs, queue.rs, types.rs)
daemon/ # Bridge daemon (mod.rs, event_loop.rs, socket_handlers.rs,
# telegram_handlers.rs, callback_handlers.rs, cleanup.rs, files.rs)
service/ # OS service management (mod.rs, systemd.rs, launchd.rs, env.rs)
rust-crates/ctm/tests/ # 10 integration test files
MIT
Built for remote Claude Code interaction from mobile devices.