← Practice · September 2, 2026
Connect a bot to the terminal: send a task from your phone and receive the response and files in the chat. We cover the setup, the “one bot — one session” limitation, and a fallback bridge for when the channel goes down.
8 min readtools

Working with an AI model in the terminal is good in every respect except one: you are tied to it. A long task runs for twenty minutes, and you cannot step away from the computer in case you need to answer a question.
Telegram solves this with an evening of setup. The bot becomes a second entry point into the same session: you write “check whether the site is up” from your phone, receive the response in the chat, and get the completed files and screenshots there. This is how I check deployments from the bus.
Below is a working setup and, more importantly, an honest list of where it breaks.
Open @BotFather, run /newbot, and choose a name and username. It responds with a token like 8805421456:AA…. This is the bot’s password, so treat it like one.
You need your own bot: there is no such thing as someone else’s bot for this purpose, and public “GPT bots” put someone else’s server between you and your terminal.
You can get your numeric Telegram ID from @userinfobot. You will need it so that the bot responds only to you.
Claude Code has an official Telegram channel: the telegram plugin. The token and allowlist are stored in the state directory:
~/.claude/channels/telegram/
.env ← TELEGRAM_BOT_TOKEN=8805421456:AA…
access.json ← who is allowed to message the bot
{
"dmPolicy": "allowlist",
"allowFrom": ["590123185"]
}
dmPolicy: allowlist is the only sensible mode. Without it, anyone who finds your bot will gain access to your terminal.
Start a session with the channel:
claude --channels plugin:telegram@claude-plugins-official
That is it: chat messages arrive in the session as ordinary messages, and responses are sent back to the chat.
Here is the first limitation worth understanding before you build a workflow around this setup.
Telegram allows only one reader per token. A second process that starts reading messages from the same bot will get a 409 Conflict error, and the processes will start taking messages from each other. So the rule is simple: one bot = one session. Three projects require three bots.
This is separated using an environment variable, with each session getting its own state directory:
#!/usr/bin/env bash
# ~/.local/bin/claude-tg <name> [claude arguments...]
set -euo pipefail
имя="${1:?usage: claude-tg <name>}"; shift
export TELEGRAM_STATE_DIR="$HOME/.claude/channels/telegram-$имя"
[ -f "$TELEGRAM_STATE_DIR/.env" ] || { echo "no token in $TELEGRAM_STATE_DIR" >&2; exit 1; }
exec claude --channels plugin:telegram@claude-plugins-official "$@"
claude-tg сайт # “Site” chat → session in the site directory
claude-tg игра # “Game” chat → its own session and bot
Create the telegram-<name> directory by copying the regular one. It has its own .env with its own token and its own access.json.
The best part of this setup is not the text but the attachments. The session can send a finished PDF, a screenshot of a page, or an archive containing an export to the chat. A screenshot of the site arrives on your phone after deployment, and you can immediately see whether everything is in place.
If the channel tools are unavailable for some reason, you can always send the file directly through the Bot API:
ТОКЕН=$(grep -o '[0-9]\{6,\}:[A-Za-z0-9_-]\+' ~/.claude/channels/telegram-сайт/.env)
ЧАТ=590123185
curl -s -F chat_id="$ЧАТ" -F caption="report is ready" \
-F document=@отчёт.pdf \
"https://api.telegram.org/bot$ТОКЕН/sendDocument"
The same logic applies to an image: use the sendPhoto method and the photo field. For text, use sendMessage and the text field.
The incoming-message reader sometimes dies silently. The session keeps running for weeks, but at some point the poller that retrieves messages goes down. The symptom is deceptive: you message the bot and get no response, while everything is still running in the terminal—there is simply nobody reading your messages. Check it with one command:
curl -s "https://api.telegram.org/bot$ТОКЕН/getWebhookInfo" | python3 -m json.tool
If pending_update_count is greater than zero and is not decreasing, the messages are sitting unread. Restarting the session fixes it; the context is preserved if you start it with --continue.
You can read them manually, but be careful. getUpdates will retrieve the stuck messages, but it will also conflict with the live reader. Use this as a one-off measure when the channel has already stopped working, not as a way to “take a peek.”
Do not forward other people’s personal data to the bot. Everything that arrives in the chat is sent to the session and may end up in the model’s context. The simple rule is: send tasks and links to the bot, not exports containing customers’ phone numbers.
Do not post tokens and passwords in the chat. It seems obvious, but the temptation to say “send me the database password” is strong. The chat history outlives your memory of what you wrote there.
Since the reader can die, it is useful to have a second, independent route. The bridge is a small script that polls Telegram itself and types the received text directly into the terminal session using tmux send-keys, then sends the response back to the chat.
Important: the bridge must have its own bot. If you run it with the same token as the plugin, they will start fighting over messages, and you will break what was working.
#!/usr/bin/env bash
# ~/.local/bin/tg-bridge <name> — “Telegram → tmux session” bridge
set -euo pipefail
имя="${1:?usage: tg-bridge <name>}"
файл="$HOME/.tg-bridge-$имя.env" # TG_TOKEN=… TG_CHAT=… TG_SESSION=…
токен=$(grep -oP '(?<=^TG_TOKEN=)\S+' "$файл")
# prevent a self-inflicted failure: this token must not be used by the plugin
if grep -rqF "$токен" "$HOME"/.claude/channels/telegram*/.env 2>/dev/null; then
echo "this token is already used by the Claude Code channel — create a separate bot" >&2
exit 1
fi
setsid nohup python3 "$HOME/.local/bin/tg-bridge.py" "$файл" \
>>"$HOME/.tg-bridge-$имя.log" 2>&1 & disown
echo "bridge “$имя” is up"
The bridge itself is twenty lines of Python: getUpdates in a loop, a sender ID check, tmux send-keys -t $TG_SESSION -l "$текст" followed by Enter, then tmux capture-pane and sending the new output back to the chat.
The setup is crude, but it works independently of whatever happened to the plugin. For me, it is strictly a fallback: the official channel is the primary route, and I start the bridge when the primary one goes silent.
It is if you work on long-running tasks and do not want to sit next to the terminal. It is not if your work consists of short, interactive edits: in that case, the phone only gets in the way.
One final observation. Telegram changes how you phrase tasks: you cannot write a fifteen-line wall of text on a phone, so the wording becomes shorter and more precise. Sometimes that benefits the work more than the mobility itself.