Laptop ở nhà. Bạn đang trên tàu điện ngầm, nhớ ra có một typo trong file config vừa push lên staging. Hoặc khách hàng nhắn “deploy lại đi, hồi nãy bị lỗi”. Mở laptop không kịp, SSH từ điện thoại thì nhập lệnh dài rất khó. Nhưng Telegram thì mở được ngay.

Pattern Telegram bridge giải quyết chính xác tình huống đó. Thay vì bạn phải đứng trước máy tính mới nói chuyện được với CC, bạn nhắn Telegram, một bot nhận message đó, chuyển vào CC session đang chạy trên server, rồi stream kết quả về lại điện thoại.

Bài này mô tả architecture của pattern, cách setup, những điểm kỹ thuật hay bị bỏ qua, và các lỗi bảo mật phổ biến khi làm bridge kiểu này.

Tại sao không chỉ SSH thẳng?

SSH từ điện thoại là được. Có app như Termius hoạt động ổn. Nhưng gõ command dài trên bàn phím ảo rất tệ, không có autocomplete, và khi lệnh chạy lâu bạn phải giữ màn hình sáng để không mất kết nối.

Telegram bridge mang lại trải nghiệm khác hẳn:

  • Nhắn text bình thường, bot xử lý.
  • CC stream kết quả về theo dạng message. Điện thoại có thể khóa màn hình, thông báo vẫn về.
  • History của session tồn tại trong chat Telegram. Bạn có thể đọc lại.
  • Dễ hơn nhiều cho các task ngắn kiểu “deploy”, “tail log”, “fix typo rồi push”.

Nhược điểm rõ nhất: không phù hợp cho task cần nhập nhiều (viết code dài từ điện thoại vẫn khó). Bridge này dùng tốt nhất cho dispatch command ngắn và nhận kết quả.

Architecture tổng quan

+----------------+     +----------+     +----------------+
| Phone Telegram | --> |   Bot    | --> | Server CC      |
| (bạn nhắn)     |     | (bridge) |     | session        |
+----------------+     +----------+     +----------------+
                              ^                 |
                              |                 |
                              +----- stdout ----+
                              (stream response về Telegram)

Ba thành phần:

  1. Telegram bot: một process Python hoặc Node.js đăng ký token với BotFather, lắng nghe message từ phone.
  2. Server: máy chủ có CC installed, có quyền thực thi task (deploy, đọc log…). Thường là máy development server hoặc homelab cá nhân.
  3. Bridge process: phần logic nối bot với CC session. Nhận message, spawn hoặc resume CC session, pipe output về bot.

Bot và bridge có thể cùng một process hoặc tách riêng, tùy độ phức tạp bạn muốn xây.

Setup bot Telegram

Đầu tiên, tạo bot qua BotFather:

  1. Nhắn /newbot cho @BotFather trong Telegram.
  2. Đặt tên và username cho bot.
  3. BotFather trả về token dạng 1234567890:ABCdef....

Token này là secret. Lưu vào file .env trên server, không commit vào repo:

TELEGRAM_BOT_TOKEN=1234567890:ABCdef...
ALLOWED_CHAT_ID=987654321

ALLOWED_CHAT_ID là chat ID của bạn (xem phần access control bên dưới). Đây là biến quan trọng nhất về security.

Access control: whitelist chat ID

Telegram bot nếu không có access control sẽ nhận message từ bất kỳ ai. Điều đó có nghĩa bất kỳ ai có username bot của bạn đều có thể ra lệnh cho CC chạy trên server.

Giải pháp đơn giản nhất là whitelist chat_id. Mỗi Telegram user có chat_id duy nhất. Bot check chat_id trước khi xử lý message:

ALLOWED_CHAT_IDS = {int(os.environ["ALLOWED_CHAT_ID"])}

async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
    chat_id = update.effective_chat.id
    if chat_id not in ALLOWED_CHAT_IDS:
        await update.message.reply_text("Unauthorized.")
        return
    # Tiếp tục xử lý

Để lấy chat_id của mình: nhắn /start cho bot, sau đó truy cập https://api.telegram.org/bot<TOKEN>/getUpdates và tìm trường "id" trong "chat".

Ngoài chat_id, bạn có thể thêm:

  • Rate limiting: tối đa N command mỗi phút, tránh spam vô tình.
  • Timeout per command: CC session chạy quá lâu thì tự cancel.
  • Command prefix: chỉ xử lý message bắt đầu bằng / hoặc prefix cụ thể, bỏ qua prose.

Bridge process: spawn hay giữ alive session?

Có hai model khác nhau:

Model 1: Spawn fresh mỗi message

Mỗi lần bạn nhắn, bridge spawn một CC subprocess mới, chờ output, trả về, kết thúc process.

message -> spawn `claude --print "..."` -> collect stdout -> reply

Ưu điểm: đơn giản, không có state leak giữa các command, dễ debug.

Nhược điểm: mỗi spawn phải load lại model, đọc lại context (CLAUDE.md, memory), tốn vài giây overhead. Không phù hợp cho multi-turn (bạn không thể hỏi follow-up trong cùng context).

Dùng flag --print của CC để chạy non-interactive:

claude --print "fix typo in src/config.ts: change 'stagin' to 'staging'"

Model 2: Giữ alive một session

Bridge duy trì một CC session, mỗi message được inject vào session đó như một user turn mới. Session có history, có context carry-over giữa các message.

Phức tạp hơn vì phải:

  • Quản lý lifecycle của session (khi nào reset, khi nào expire).
  • Pipe input vào stdin của process đang chạy.
  • Detect khi nào CC đã xong một turn để reply (không phải cứ stdout flush là xong).

Với SDK, model 2 dễ hơn vì SDK expose session_idresume API rõ ràng. Với binary CLI, bạn có thể dùng --resume <session-id> nhưng cần lưu session ID sau mỗi lần dùng.

Cho đa số use case mobile (dispatch command, không cần deep conversation), model 1 là đủ và ít rủi ro hơn.

Giới hạn 4096 ký tự của Telegram

Telegram giới hạn mỗi message tối đa 4096 ký tự. CC output dài hơn thường gặp khi chạy diff, log tail, hoặc list file.

Hai cách xử lý:

Chunk output: Chia output thành nhiều message liên tiếp, mỗi message dưới 4096 ký tự.

MAX_LEN = 4096

def chunk_and_send(text: str, chat_id: int):
    for i in range(0, len(text), MAX_LEN):
        bot.send_message(chat_id, text[i:i + MAX_LEN])

Đơn giản nhưng 10 message liên tiếp khá khó đọc trên điện thoại.

Upload as file: Nếu output dài hơn ngưỡng (ví dụ 2000 ký tự), gửi dưới dạng .txt file attachment thay vì text.

if len(output) > 2000:
    with open("/tmp/cc_output.txt", "w") as f:
        f.write(output)
    bot.send_document(chat_id, open("/tmp/cc_output.txt", "rb"))
else:
    bot.send_message(chat_id, output)

File attachment thường tiện hơn khi output là log hoặc diff dài. Bạn có thể mở trong Telegram Files và đọc thoải mái.

Use case thực tế

Các command ngắn phù hợp nhất với pattern này:

Trigger deploy:

/deploy staging

Bot nhận, spawn CC với instruction "Run deploy to staging environment", trả về status.

Tail log:

tail last 50 lines of container app-server

CC chạy docker logs --tail 50 app-server, trả về output.

Fix nhanh rồi push:

fix typo in src/landing/hero.astro: "Welcom" -> "Welcome", then commit and push

CC đọc file, sửa, commit, push. Trả về commit hash.

Lưu ý: với command có destructive action (push, deploy, rm…), bạn nên thêm confirmation step. CC hỏi “Confirm deploy to staging?” trước khi thực thi. Xem phần best practice bên dưới.

Secret handling trên server

Bot process cần token Telegram và potentially các credential khác (GitHub token, server SSH key, cloud credentials). Quy tắc cứng:

  • Tất cả secret vào .env file trên server.
  • .env không commit vào repo.
  • Process load qua python-dotenv hoặc tương đương.
  • Không hardcode secret trong bot script, kể cả trong comment.
from dotenv import load_dotenv
load_dotenv()

TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
ALLOWED_CHAT_IDS = {int(os.environ["ALLOWED_CHAT_ID"])}

Nếu dùng systemd service để giữ bot chạy liên tục, truyền env var qua file EnvironmentFile trong unit config, không phải inline trong ExecStart.

Anti-pattern nguy hiểm: mở bypassPermissions cho mobile

Đây là lỗi phổ biến khi dev muốn bot “không hỏi lại” để tiện dùng trên điện thoại.

# ĐỪNG làm thế này
claude --dangerously-skip-permissions --print "$message"

bypassPermissions tắt toàn bộ permission check. Kết hợp với mobile bridge có nghĩa là bất kỳ command nào bạn nhắn (hoặc bất kỳ ai nếu whitelist bị bỏ qua) đều chạy mà không cần xác nhận. Một typo trong command có thể xóa file, drop database, hoặc push code sai lên production.

Như đã nói ở bài 4, bypassPermissions từng gây ra sự cố mất data thực trong môi trường production. Tình huống đó không phải là qua mobile bridge, nhưng cơ chế tai nạn là như nhau: một lệnh nguy hiểm chạy mà không có điểm dừng.

Pattern đúng cho mobile bridge:

  1. Dùng permission ask mode bình thường, nhưng với non-interactive flow (--print), CC sẽ dừng khi gặp action cần confirm.
  2. Hoặc: allowlist chính xác các tool CC được dùng trong bridge context. Ví dụ chỉ cho Read, Bash (với giới hạn command), không cho Write trực tiếp.
  3. Với destructive command (deploy, push, rm): thêm confirmation bằng Telegram. Bot hỏi “Xác nhận không?”, bạn reply “yes”, rồi mới chạy.

Best practice tổng hợp

Hạng mụcKhuyến nghị
Access controlWhitelist chat_id bắt buộc
Secret.env trên server, không commit
PermissionKhông dùng bypassPermissions
Destructive actionYêu cầu confirm message trước khi chạy
Long outputChunk hoặc upload as file
LoggingLog mọi message nhận được với timestamp
Session modelSpawn fresh cho command đơn lẻ, SDK cho multi-turn
Rate limitTối đa N command/phút để tránh spam vô tình

Logging đặc biệt quan trọng. Vì bạn dùng từ điện thoại, sau đó mở laptop lên bạn cần biết chính xác bot đã làm gì, theo thứ tự nào. Log cả message đến, response từ CC, và bất kỳ lỗi nào.

Alternative: Slack bridge, Discord bridge

Pattern này không phụ thuộc vào Telegram. Bạn có thể build cùng architecture với:

  • Slack: Slack App với Slash Command và Incoming Webhook. Phù hợp cho team có Slack sẵn.
  • Discord: Discord Bot với message event. Phù hợp nếu team dùng Discord.
  • WhatsApp: qua Twilio API. Phức tạp hơn, cần account Twilio.

Core logic (access control, session management, output chunking) giống nhau. Chỉ khác ở SDK của từng platform.

Nếu team bạn đã có Slack workspace thì Slack bridge tích hợp tự nhiên hơn, không cần mọi người cài thêm app. Điều này đặc biệt hữu ích khi bạn muốn một số teammate cũng có thể trigger command qua bridge (với permission của họ).

Notification-only vs full bridge

Có một use case đơn giản hơn full bridge: notification-only. Thay vì nhận command từ điện thoại, bot chỉ gửi notification về khi CC làm xong việc gì đó.

Ví dụ: CC deploy xong, hook Stop gọi bot gửi “Deploy hoàn thành, commit abc123” về Telegram. Bạn không cần mở laptop để biết kết quả.

Đây thực ra là pattern đơn giản hơn nhiều và đã được implement qua hook Stop trong settings. Bài 25 sẽ đi sâu vào cách viết hook production-grade cho notification pattern này: retry logic, format message, handle error, v.v.

Full bridge (nhận command từ phone) và notification-only (chỉ gửi kết quả về) có thể dùng song song: bridge để dispatch task khi đang ngoài đường, notification để biết kết quả mà không cần ngồi chờ.

Tóm tắt và bài tiếp theo

  • Telegram bridge là một pattern hợp lý cho mobile coding: bot nhận command từ điện thoại, forward vào CC session trên server, stream response về Telegram.
  • Ba thành phần: bot (Python/Node.js), server với CC, bridge process nối hai thứ lại.
  • Thách thức kỹ thuật chính: access control qua chat_id whitelist, giới hạn 4096 ký tự, quản lý lifecycle session.
  • Anti-pattern nguy hiểm nhất: mở bypassPermissions để tiện dùng. Không làm vậy.
  • Notification-only (hook Stop) thường đủ dùng và đơn giản hơn nhiều so với full bridge.

Bài 25 sẽ xây hook Stop production-grade: gửi notification về Telegram khi CC xong task, với retry khi network flaky, format message rõ ràng, và handle các edge case thường bị bỏ qua khi build vội.


Bài thuộc series Claude Code từ zero. Series plan tại bài giới thiệu.