Talk to a Claude Code CLI from Telegram, with persistent per-chat memory.
A small, zero-dependency bridge: it long-polls Telegram, accepts messages only
from user ids you allowlist, and for each one spawns claude -p --resume in a
directory you choose — so your phone reaches a Claude that carries its own memory
across turns and thinks with that directory's full context.
It is generic by construction: it knows nothing about any one project. Point it at a directory, lock it to your Telegram id, and go.
Built for the Claude Code CLI
(and, gladly, for Anthropic) — that's the default and the intended target. But the
CLI it spawns is just a configurable claude_binary: point it at any other agent
CLI that speaks a compatible headless interface (a one-shot prompt, a JSON result,
a resumable session id) and it drives that instead. Claude is what it's tuned for;
it isn't locked to Claude.
You (Telegram) ──► long-poll getUpdates ──► allowlist gate ──► claude -p --resume
▲ │
└────────────── code-aware MarkdownV2 reply (paced, typing UX) ◄─────┘
This bridges your phone to a code-executing agent on your own machine, running
as you. A Telegram message becomes a claude spawn in your working_dir —
with unattended tool execution (bash, git, file writes) if you enable it. The
trust model, in three lines:
- Self-hosted, bring-your-own-bot. You run this on your own machine, with your own bot and your own Claude login. One install serves one person; there is deliberately no shared or multi-user mode — "multiple users" means each person runs their own copy.
- The allowlist is the entire door.
allowed_user_idsis the only authentication. An empty list refuses to start (fail-closed). Anyone on the list can make the agent do everything the agent can do — list only yourself, or people you trust at exactly that level. - The bot token is a password. It lives only in the gitignored
config.toml. If it ever leaks, rotate it via @BotFather.
Full detail, including the write-jail and the bypass interlock, in SECURITY.md.
- Persistent memory — each chat maps to a stable Claude session id; replies
use
--resume, so context survives across messages and restarts (SQLite). - Allowlist is the only door — no inbound ports, no roles. Unlisted ids are silently dropped. See SECURITY.md.
- Code-aware replies — Claude's fenced code blocks render as Telegram monospace; prose is MarkdownV2-escaped. Long replies are split on safe boundaries, paced at 1 msg/sec, with a live "typing…" indicator.
- Optional write-jail — confine the spawn's file writes to your project: a Seatbelt profile on macOS, or the systemd unit itself on Linux — the installer generates either. Required (interlocked) if you enable unattended tool execution.
- Zero dependencies — Python standard library only (
urllib,sqlite3,tomllib). No pip install.
- Python ≥ 3.11 (needs
tomllib). - The Claude Code CLI,
installed and logged in (run
claudeonce interactively first). The bot uses your existing CLI authentication — no API key is passed. - A Telegram account.
- The core runs anywhere the Claude CLI runs. The optional write-jail ships for
macOS (Seatbelt profile) and Linux/systemd (the unit in
deploy/).
1. Create a bot and get its token. In Telegram, message
@BotFather → /newbot → follow prompts. It gives you a
token shaped like 123456789:AA....
2. Get your numeric user id. Message @userinfobot;
it replies with your id (a number like 123456789).
3. Clone and configure.
git clone <your-fork-url> claude-telegram-interface
cd claude-telegram-interface
cp config.example.toml config.toml # config.toml is gitignoredEdit config.toml:
bot_token = "123456789:AA...your-token..."
allowed_user_ids = [123456789] # YOUR id from step 2
working_dir = "/absolute/path/to/the/project/claude/should/work/in"4. Install the secret gate (recommended). Stops you ever committing the token:
cp scripts/pre-commit-secret-gate.sh .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit5. Run it.
python3 bot.pyYou should see a boot line like bot @your_bot up | allowlist=[...] | jail=off | bypass=off.
Now message your bot from Telegram. The first reply starts a fresh session;
follow-ups resume it — ask it to recall something you said earlier to prove it.
Optional: copy
examples/run.shto the repo root for a launcher that pins cwd and lets you point at a specific Python (PYTHON=/path/to/python3 ./run.sh).
All settings live in config.toml. Required: bot_token, allowed_user_ids,
working_dir. Everything else has a default — see
config.example.toml for the full annotated list.
| Key | Default | Purpose |
|---|---|---|
bot_token |
— | From @BotFather. <id>:<secret>. |
allowed_user_ids |
— | Numeric Telegram ids allowed to use the bot. The only gate. |
working_dir |
— | Directory the Claude CLI runs in (its context loads from here). |
claude_binary |
claude |
Path/name of the Claude Code CLI. Use an absolute path under a service. |
model |
CLI default | e.g. claude-opus-4-8. |
sandbox_profile |
(none) | Path to a macOS Seatbelt profile (write-jail). |
linux_confined |
false |
Attests the bot runs under the confining systemd unit from deploy/ (the Linux write-jail). |
bypass_permissions |
false |
Run tools without prompts. Requires a jail: sandbox_profile (macOS) or linux_confined (Linux). |
poll_timeout |
40 |
Long-poll seconds for getUpdates. |
spawn_timeout |
600 |
Max seconds for one spawn. |
drain_hours |
8 |
Idle this long → next message starts a fresh session. |
db_path / log_path |
.claude-telegram-interface/… |
State DB and log file. |
By default the headless spawn runs in Claude's normal permission mode, so actions
that would need interactive approval (writes, bash) are denied — the bot is
effectively read-and-converse. To let it act (run bash/git/etc.) without a
prompt, set bypass_permissions = true.
This is interlocked for safety: the bot refuses to start with
bypass_permissions = true unless a write-jail is in place.
- macOS: run
./install.shand answer Y to tool execution — it generates the Seatbelt profile and pointssandbox_profileat it. (Manual path: copyexamples/example.sb, replace the placeholder paths.) The jail confines writes to your project (and~/.claude, which--resumeneeds) while leaving network/Keychain open. - Linux: the systemd unit in
deploy/is the jail (unprivilegedUser=+ProtectSystem=strict+ scopedReadWritePaths). Setlinux_confined = trueto attest it's in place; the bot additionally refuses to run bypass as root.
Read SECURITY.md before enabling this.
A long-poll bot needs its host awake and on the network. The guided installer detects your OS and sets up its native service:
# macOS — no sudo; installs a per-user LaunchAgent:
./install.sh
# Linux — root places the systemd unit; everything else stays in your account:
sudo ./install.shinstall.sh is one readable bash file — inspect it before running it (on
Linux, root is used only to place the systemd unit; everything else stays in
your account). Both paths verify the Claude CLI actually runs under the
service's exact environment (catching the "works in my shell, dies under the
service manager" class at install time), ask for your bot token (verified live
against Telegram) and user id, write the config, and install.
The installer copies the whole app into ~/.claude-telegram-interface/ and the
service runs from there — the clone/ZIP you ran it from is disposable
afterwards (a config.toml you already created in step 3 is migrated in).
Re-run later from the installed copy — ~/.claude-telegram-interface/install.sh
(macOS) or sudo ~/.claude-telegram-interface/install.sh (Linux) — e.g. after
editing the config. Updating = download the new version anywhere and run its
install.sh: code is refreshed in place, your config.toml is kept.
What gets installed:
- macOS: a LaunchAgent in
~/Library/LaunchAgents(survives logout/crash;caffeinate -skeeps the Mac awake on AC power — on battery the bot pauses with the Mac's sleep and resumes on wake). Enabling tool execution also generates the Seatbelt write-jail and wires it into the config. - Linux: a systemd service that doubles as the write-jail. Full walkthrough: deploy/DEPLOY-LINUX.md.
For true always-on, a Linux VPS is the better host. Prefer manual setup?
examples/ has a hand-fill LaunchAgent plist and Seatbelt profile.
The bridge is complete standalone. But working_dir decides what the agent
is when a message wakes it: pointed at a directory built for persistent AI
partnership, the same bot becomes a full working seat — your projects and
shared memory, reachable from a phone. The recommended setup (the Flow
methodology + an episodic memory CLI, by Phill Clapham) and the reasoning
behind it live in docs/RECOMMENDED-SETUP.md.
Ships: run-anywhere core, write-jail on macOS (Seatbelt) and Linux (systemd unit + guided installer), TOML config, single-allowlist auth, code-aware MarkdownV2, reconnect + 429 backoff, typing UX.
Out of scope (v1): multi-user roles, plugins, web dashboard, inline buttons/rich UX. Generic-but-minimal on purpose.
Maintained as-is by a solo developer: issues and PRs are welcome, response times are not guaranteed, and there is no SLA. This is an independent project, not affiliated with or endorsed by Anthropic or Telegram. A Claude subscription covers individual use — this bridge is shaped for exactly that (your bot, your login, your machine, your own traffic only) — but verifying your own plan's terms is on you.
MIT — see LICENSE.