Claude Code Telegram Bridge
Single-binary Rust service bridging Telegram to Claude Code sessions
I run Claude Code on a headless server and wanted to send it tasks from my phone. The first version was Python, and it worked, but it leaked memory, started slowly, and had a session-handling bug I could not reproduce. Rewriting it in Rust fixed all three problems and cut resource usage by an order of magnitude.
A single binary that stays up indefinitely as a systemd service. Session state persists across restarts. Markdown formats correctly in Telegram. Voice messages transcribe locally via whisper.cpp. The only dependency at runtime is Claude Code itself.
Session persistence across restarts
Sessions live in a JSON file mapping Telegram user IDs to Claude session IDs. The format is compatible with the Python predecessor for zero-migration switchover. On startup it loads into an Arc<Mutex<>>. If Claude returns a session-expired error, the handler removes the stale entry and retries with a fresh session automatically.
Markdown to Telegram HTML
Comrak parses the response into a CommonMark AST. A custom walker converts it to the subset of HTML that Telegram supports: bold, italic, code blocks, pre, links. Messages over 4096 characters split at paragraph boundaries with HTML tags closed and reopened across chunks. If the HTML still fails Telegram's parser, the handler strips formatting and sends plain text.
systemd-notify watchdog
The binary registers as Type=notify with systemd. A background tokio task pings the Telegram API every 60 seconds and calls sd_notify on success. After 10 consecutive polling failures, the process exits. systemd restarts it automatically. No external monitoring scripts; the health check is part of the binary.
An order of magnitude leaner in Rust
The Python predecessor was slow to start and heavy in memory. The Rust version starts in a few milliseconds instead of hundreds and sits in single-digit MB instead of tens. For a service running around the clock on a home server, the resource savings add up. The rewrite also caught a session-handling race the Python version had been silently ignoring.
Minimal deployment
One binary, one .env file, one systemd unit. No runtime, no container, no package manager dependency. cargo install builds it, systemctl manages it.
Self-healing error paths
Expired sessions are detected and retried. HTML formatting failures fall back to plain text. Polling failures accumulate until the watchdog exits and systemd restarts. From the user's side, problems show up as a brief delay, not an error message.
Multi-user path
Session isolation is already keyed by Telegram user ID. Adding rate limiting per user, a queue for concurrent Claude invocations, and proper multi-user access control would turn this from a personal tool into something deployable for a small team.
See the rest of the work.