AI를 활용한 재밌는 것들을 개발합니다

2026년 4월 30일 목요일

텔레그램으로 Claude Code 원격 제어하기 — 설치부터 실전까지 [Claude Code · 텔레그램봇 · 완결편]

Claude Code Python Telegram Bot

텔레그램으로 Claude Code 원격 제어하기
설치 오류 해결부터 실전 봇 완성까지

스마트폰 텔레그램 앱에서 메시지 하나로 PC의 Claude Code를 실행하는 봇을 직접 만들어봤습니다.

왜 만들었나

유튜브에서 텔레그램으로 Claude Code를 원격 실행하는 영상을 보고 바로 따라해보고 싶었습니다. PC 앞에 없어도 스마트폰으로 코드 작업을 시킬 수 있다면 꽤 쓸만하겠다 싶었거든요. 막상 시작하니 PATH 오류부터 네트워크 에러까지 자잘한 문제가 연달아 터졌는데, 이 글에 그 과정을 그대로 담았습니다.

[텔레그램 앱] ──메시지──▶ [Telegram Bot API] │ [bot.py — Python] │ ┌──────────────▼──────────────┐ │ claude --print "메시지" │ │ cwd = D:\ClaudeCode │ │ --dangerously-skip-perms │ └──────────────┬──────────────┘ │ 응답 텍스트 [텔레그램 앱] ◀──────────────

준비물

항목비고
Claude Code CLIAnthropic 공식 CLI — claude 명령어
Python 3.x3.11 기준으로 진행
pyTelegramBotAPIpip install pyTelegramBotAPI python-dotenv
Telegram 계정봇 생성용 (@BotFather)

1단계 — Claude Code 설치 및 PATH 설정

Claude Code를 설치하면 실행 파일이 C:\Users\{사용자}\.local\bin에 생성됩니다. Windows 환경변수에 이 경로를 추가했는데도 PowerShell에서 claude가 인식되지 않는 문제가 생겼습니다.

🔴 문제: PowerShell에서 claude 명령어를 찾을 수 없음
환경변수에 C:\Users\jinwh\.local\bin을 추가했지만 PowerShell에서 claude를 실행하면 "찾을 수 없다"는 오류가 발생. VS Code를 재시작해도 동일.

원인을 파보니 Git Bash와 PowerShell은 PATH를 읽는 방식이 달랐습니다. Git Bash용 .bashrc를 아무리 수정해도 PowerShell엔 적용이 안 됩니다. PowerShell 전용 프로필을 따로 수정해야 합니다.

✅ 해결: PowerShell 프로필에 PATH 추가
PowerShell에서 아래 명령어를 실행하면 $PROFILE 파일에 경로가 추가되어 새 세션부터 영구 적용됩니다.
# 프로필 파일이 없으면 폴더 먼저 생성
New-Item -ItemType Directory -Path (Split-Path $PROFILE) -Force

# PATH에 claude 경로 추가
Add-Content -Path $PROFILE -Value '$env:PATH += ";C:\Users\{사용자명}\.local\bin"' -Encoding utf8
확인 방법
VS Code를 완전히 닫았다가 다시 열고 PowerShell 터미널에서 claude --version을 입력. 2.x.x (Claude Code)가 출력되면 성공입니다.

2단계 — Telegram Bot 생성 (@BotFather)

1
텔레그램 앱에서 @BotFather를 검색해 파란 체크마크 공식 계정을 선택하고 Start를 누릅니다.
2
/newbot을 입력하면 봇 이름과 username을 차례로 물어봅니다. username은 반드시 bot으로 끝나야 합니다. (예: MyClaude_bot)
3
완료되면 Bot Token이 발급됩니다. 1234567890:ABCdef... 형태의 문자열로, 이후 모든 API 호출에 사용됩니다.
⚠️ 토큰은 비밀번호입니다
토큰이 유출되면 누구든 내 봇을 제어할 수 있습니다. 코드에 직접 넣지 말고 .env 파일에 보관하세요. 만약 유출됐다면 BotFather에서 /revoke로 즉시 재발급하세요.

3단계 — bot.py 작성

프로젝트 폴더에 두 파일만 만들면 됩니다. 토큰은 .env에 분리 보관합니다.

# .env
TELEGRAM_BOT_TOKEN=여기에_발급받은_토큰_입력
# bot.py
import telebot
import subprocess
import os
from dotenv import load_dotenv

load_dotenv()

bot = telebot.TeleBot(os.environ.get("TELEGRAM_BOT_TOKEN"))


@bot.message_handler(commands=["start", "help"])
def handle_start(message):
    bot.reply_to(message, "안녕하세요! Claude Code 봇입니다.")


@bot.message_handler(func=lambda message: True)
def handle_message(message):
    bot.send_message(message.chat.id, "⏳ 처리 중...")

    try:
        result = subprocess.run(
            ["claude", "--print", "--dangerously-skip-permissions", message.text],
            capture_output=True,
            text=True,
            encoding="utf-8",
            timeout=120,
            cwd=r"D:\ClaudeCode",  # Claude가 접근할 작업 디렉토리
        )
        response = result.stdout.strip() or result.stderr.strip() or "응답이 없습니다."
    except subprocess.TimeoutExpired:
        response = "시간 초과 (2분). 더 짧은 질문을 시도해보세요."
    except FileNotFoundError:
        response = "claude 명령어를 찾을 수 없습니다. PATH를 확인하세요."
    except Exception as e:
        response = f"오류 발생: {e}"

    # Telegram 메시지 최대 4096자 제한 처리
    for i in range(0, len(response), 4096):
        bot.send_message(message.chat.id, response[i:i+4096])


print("봇 시작됨. 종료하려면 Ctrl+C")
bot.infinity_polling(timeout=20, long_polling_timeout=15)
핵심 옵션 두 가지
--dangerously-skip-permissions — 파일 읽기/쓰기 시 매번 뜨는 권한 확인 프롬프트를 생략. 자동화 실행에 필수입니다.
cwd=r"D:\ClaudeCode" — Claude가 이 폴더를 기준으로 파일을 탐색하고 수정합니다.

4단계 — 보안 강화

봇을 처음 만들고 나서 문득 깨달았습니다. 봇 username만 알면 누구든 메시지를 보낼 수 있고, 그 메시지가 그대로 내 PC의 Claude Code로 전달된다는 것을. --dangerously-skip-permissions까지 붙어있으니 사실상 내 PC에 원격 접근 권한을 열어둔 것과 다름없는 상태였습니다.

🔴 문제: 인증 없이 누구나 봇에 접근 가능
텔레그램 봇은 기본적으로 공개 상태입니다. 봇 username을 아는 사람이라면 누구든 메시지를 보낼 수 있고, 그 메시지가 내 PC의 Claude Code로 그대로 전달됩니다.

본인 user_id 확인하기

텔레그램에서 @userinfobot에게 아무 메시지나 보내면 숫자로 된 본인 ID를 알려줍니다. 이 ID를 화이트리스트에 등록합니다.

user_id 화이트리스트 + 추가 보완 3가지

화이트리스트 적용과 함께 점검 중 발견한 3가지 문제도 함께 수정했습니다.

문제조치
누구나 접근 가능user_id 화이트리스트 — 등록된 ID 외 모든 메시지 무시
사진·스티커 전송 시 크래시message.text None 체크 후 안내 메시지 반환
작업 중 중복 실행threading.Lock()으로 동시 요청 차단
토큰 미설정 시 조용한 실패시작 시점에 RuntimeError로 즉시 종료
# bot.py — 보안 강화 최종본
import telebot
import subprocess
import os
import threading
from dotenv import load_dotenv

load_dotenv()

BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN")
if not BOT_TOKEN:
    raise RuntimeError(".env 파일에 TELEGRAM_BOT_TOKEN이 없습니다.")

ALLOWED_USER_ID = 본인_user_id_숫자   # @userinfobot 에서 확인

bot = telebot.TeleBot(BOT_TOKEN)
_processing = threading.Lock()


def is_authorized(message):
    return message.from_user.id == ALLOWED_USER_ID


@bot.message_handler(commands=["start", "help"])
def handle_start(message):
    if not is_authorized(message):
        return   # 무시 — 봇 존재 여부조차 노출 안 함
    bot.reply_to(message, "안녕하세요! Claude Code 봇입니다.")


@bot.message_handler(func=lambda message: True)
def handle_message(message):
    if not is_authorized(message):
        return

    if not message.text:
        bot.reply_to(message, "텍스트 메시지만 지원합니다.")
        return

    if not _processing.acquire(blocking=False):
        bot.reply_to(message, "이전 작업이 진행 중입니다. 잠시 후 다시 시도하세요.")
        return

    try:
        bot.send_message(message.chat.id, "⏳ 처리 중...")
        result = subprocess.run(
            ["claude", "--print", "--dangerously-skip-permissions", message.text],
            capture_output=True, text=True, encoding="utf-8",
            timeout=120, cwd=r"D:\ClaudeCode",
        )
        response = result.stdout.strip() or result.stderr.strip() or "응답이 없습니다."
    except subprocess.TimeoutExpired:
        response = "시간 초과 (2분). 더 짧은 질문을 시도해보세요."
    except FileNotFoundError:
        response = "claude 명령어를 찾을 수 없습니다. PATH를 확인하세요."
    except Exception as e:
        response = f"오류 발생: {e}"
    finally:
        _processing.release()

    for i in range(0, len(response), 4096):
        bot.send_message(message.chat.id, response[i:i+4096])


print("봇 시작됨. 종료하려면 Ctrl+C")
bot.infinity_polling(timeout=20, long_polling_timeout=15)
다른 사람이 메시지를 보내면?
return으로 조용히 무시합니다. 아무 응답도 보내지 않기 때문에 봇이 존재하는지조차 알 수 없습니다.

트러블슈팅 — ConnectTimeout 오류

봇이 잘 돌아가다가 갑자기 아래 에러를 내뱉고 죽는 경우가 있었습니다.

🔴 ConnectTimeout: api.telegram.org 연결 시간 초과
requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host='api.telegram.org', port=443): Max retries exceeded

네트워크가 일시적으로 불안정할 때 Telegram 서버 연결이 끊기면서 발생. bot.polling()은 이 오류를 처리하지 못하고 프로세스 자체가 종료됩니다.
✅ 해결: infinity_polling()으로 교체
bot.polling(none_stop=True) 대신 bot.infinity_polling()을 사용하면 ConnectTimeout, ConnectionError 등 네트워크 오류 발생 시 자동으로 재연결을 시도합니다. 봇이 혼자 죽지 않습니다.
# 변경 전
bot.polling(none_stop=True)

# 변경 후 — 네트워크 오류 자동 재연결
bot.infinity_polling(timeout=20, long_polling_timeout=15)

실행 및 결과

1
PowerShell에서 python bot.py 실행 → 봇 시작됨. 출력 확인
2
텔레그램에서 내 봇 계정을 열고 아무 메시지나 전송
3
⏳ 처리 중... 메시지 후 Claude의 응답이 돌아오면 완성
✅ 완성된 기능 체크리스트

✔ 텔레그램 메시지 → Claude Code CLI 실행
✔ D:\ClaudeCode 폴더 전체 파일 접근 권한
✔ 권한 확인 프롬프트 없이 자동 실행
✔ 4096자 초과 응답 자동 분할 전송
✔ 네트워크 오류 시 자동 재연결
✔ user_id 화이트리스트 — 본인 외 접근 차단
✔ 비텍스트 메시지(사진·스티커) 크래시 방지
✔ 중복 실행 방지 (Lock)
✔ 토큰 미설정 시 시작 단계에서 즉시 오류 감지

마치며

코드 자체는 60줄 남짓으로 단순하지만, PATH 설정 하나 때문에 삽질하는 데 시간이 꽤 걸렸습니다. Windows에서 Git Bash와 PowerShell의 PATH가 따로 논다는 걸 이번에 제대로 알게 됐습니다. 그리고 봇을 완성하고 나서야 "아무나 접근할 수 있겠구나"를 깨닫고 보안을 덧붙였는데, 처음부터 보안을 고려하고 만드는 습관이 필요하다는 걸 느꼈습니다. Claude Code를 텔레그램으로 연결하면 PC를 켜놓고 외출해도 스마트폰에서 코드 작업을 시킬 수 있어서 활용 폭이 꽤 넓어집니다.

최종 파일 구조

D:\ClaudeCode\ClaudeByTelegram\
├── bot.py          ← 봇 메인 코드
├── .env            ← 텔레그램 토큰 (비공개)
└── .gitignore      ← .env 제외

댓글 없음:

댓글 쓰기

가장 많이 본 글