Claude Code Channels 완전 가이드: Telegram 설정, 데몬화, 커스텀 채널 빌드
목차
Claude Code v2.1.80부터 Research Preview로 제공되는 Channels. MCP 서버가 실행 중인 세션에 외부 이벤트를 푸시하는 구조로, Telegram이나 Discord에서 개발 세션과 양방향 대화가 가능하다.

Claude Code는 원래 터미널에서 사용자가 직접 입력하는 것 외에, 실행 중인 세션에 외부에서 메시지를 넣을 방법이 없었다. OpenClaw, NanoClaw 같은 프로젝트가 Claude Agent SDK 위에 별도 호스트 프로세스를 올리고 채팅 플랫폼을 연결했던 것도 이런 한계 때문이었다. 이들이 구현한 컨테이너 격리, 멀티 그룹 관리, 메시지 라우팅은 Channels의 범위 밖이다. v2.1.80의 Channels는 “세션에 메시지를 넣는 경로”라는 핵심 문제에 대한 공식 답이다.
Channel이란
MCP 프로토콜 위에 채널 플러그인을 연결하면, 외부 플랫폼의 메시지가 실행 중인 세션에 직접 도착하는 양방향 통신 경로다. 단순히 이벤트를 받기만 하는 게 아니라, Claude가 같은 경로로 응답을 돌려보낸다. Telegram에서 메시지를 보내면 Claude Code 세션이 받아서 처리하고, reply tool로 Telegram에 응답을 보내는 구조다.
sequenceDiagram
participant T as Telegram
participant P as MCP Plugin
participant C as Claude Code Session
T->>P: 사용자 메시지
P->>C: channel 이벤트 푸시
C->>C: 작업 수행 (파일 읽기, 코드 수정 등)
C->>P: reply tool 호출
P->>T: 응답 전송물론 reply tool 없이 단방향(알림 수신만)으로 만들 수도 있다. 이건 뒤에서 커스텀 채널을 다룰 때 다시 나온다.
세 가지 특성이 이 구조를 지탱한다.
- MCP 기반: 채널의 실체는 MCP 서버다. Claude Code가 subprocess로 실행하고 stdio로 통신한다. 기존 MCP 생태계와 동일한 구조이므로 별도 프로토콜을 배울 필요가 없다.
- Polling 방식: Telegram 플러그인은 Bot API를 polling한다. Webhook URL을 노출할 필요가 없어서 공인 IP가 없는 개인 환경에서도 동작한다.
- 세션 스코프: 채널은 실행 중인 세션에 종속된다. 세션을 닫으면 채널도 사라진다. (상시 동작이 필요하면 tmux와 launchd로 데몬화할 수 있다. 뒤에서 다룬다.)
이 세 가지가 어떻게 맞물리는지 정리하면 이렇다.
flowchart RL
TG["Telegram App"] -->|"message"| API
API -->|"response"| TG
subgraph polling["Polling"]
API["Telegram Bot API"]
end
API -->|"new message"| MCP
MCP -->|"polling / sendMessage"| API
subgraph session["Session Scope"]
MCP["MCP Channel Server<br>(subprocess, stdio)"]
CC["Claude Code Session"]
end
MCP -->|"channel event"| CC
CC -->|"reply tool"| MCP
style session fill:#f0f0ff,stroke:#6666cc
style polling fill:#fff0f0,stroke:#cc6666현재 지원되는 채널은 세 가지다. 모든 채널 플러그인은 Bun 런타임이 필요하다.
채널 플러그인은 TypeScript로 작성된 MCP 서버다. 이
.ts파일을 실행할 런타임이 필요한데, **Bun**은 TypeScript를 빌드 없이 바로 실행할 수 있어서 공식 플러그인의 기본 런타임으로 채택되었다. Node.js로도 가능하지만 별도 컴파일 단계가 필요하다. Bun이 없으면 플러그인 설치 시 안내가 나온다.brew install oven-sh/bun/bun으로 설치한다.
| 채널 | 특징 |
|---|---|
| Telegram | polling 방식, 모바일에서 개발 세션 접근 |
| Discord | Socket 기반, Message Content Intent 활성화 필요 |
| fakechat | localhost:8787 데모 UI, 테스트/학습용 |
Telegram 채널 설정
설정은 네 단계다. BotFather에서 봇을 만들고, 플러그인을 설치하고, 채널을 활성화하고, 페어링으로 보안을 설정한다.
1단계: BotFather에서 봇 생성
Telegram에서 @BotFather를 찾아 /newbot 명령을 보낸다. 봇의 표시 이름과 bot으로 끝나는 username을 지정하면 토큰이 발급된다.
2단계: 플러그인 설치 및 토큰 설정
Claude Code 세션을 실행한 상태에서 플러그인을 설치하고 토큰을 등록한다.
# 플러그인 설치
/plugin install telegram@claude-plugins-official
# 토큰 설정
/telegram:configure <BotFather에서 받은 토큰>토큰은 ~/.claude/channels/telegram/.env(user 레벨) 또는 프로젝트의 .claude/channels/telegram/.env에 저장된다. 환경변수 TELEGRAM_BOT_TOKEN으로 직접 설정하는 것도 가능하다. 토큰 자체는 user 레벨에 두어도 문제없다 — 봇 인증 정보일 뿐 polling을 시작하지 않는다.
주의할 것은 플러그인 활성화 설정(enabledPlugins)이다. 이것이 user 레벨(~/.claude/settings.json)에 있으면 모든 세션에서 플러그인이 로드되면서 polling이 시작된다. 데몬 세션을 운영한다면 polling 충돌이 발생하므로, project 레벨(settings.local.json)에 두는 것이 안전하다.
주의:
enabledPlugins를 user 레벨(~/.claude/settings.json)에 두면 모든 세션에서 polling이 시작되어 데몬 세션의 연결을 끊는다. 반드시 project 레벨(settings.local.json)에서만 활성화한다. 이 문제로 실제 장애를 겪은 상세 사례는 글 마지막 에피소드를 참조한다.
3단계: 채널 활성화
Claude Code를 --channels 플래그와 함께 시작한다. 이 플래그가 없으면 MCP 서버는 연결되지만 채널 이벤트는 도착하지 않는다.
claude --channels plugin:telegram@claude-plugins-official여러 채널을 동시에 쓸 수도 있다. 공백으로 구분하면 된다.
claude --channels plugin:telegram@claude-plugins-official plugin:discord@claude-plugins-official4단계: 페어링 및 접근 제어
Telegram에서 방금 만든 봇에게 아무 메시지나 보낸다. 봇이 페어링 코드를 응답한다. 이 코드를 Claude Code 세션에서 승인하면 발신자 ID가 allowlist에 등록된다. allowlist의 동작 원리와 보안 구조는 바로 아래 보안 모델 섹션에서 자세히 다룬다.
# 페어링 코드 승인
/telegram:access pair <코드>
# allowlist 정책 적용 — 등록된 사용자만 메시지 전달
/telegram:access policy allowlist설정 흐름 전체
/newbot/plugin install/telegram:configure--channelspolling 시작
보안 설정 완료
보안 모델
채널은 외부에서 Claude Code 세션으로 텍스트를 주입하는 경로이기 때문에, 보안이 중요하다. 세 겹의 보호가 있다.
Sender Allowlist — 모든 채널 플러그인은 발신자 allowlist를 관리한다. 등록되지 않은 ID에서 온 메시지는 조용히 무시된다. 이 검증은 발신자 ID 기준이다. 채팅방이나 그룹 ID가 아니라 메시지를 보낸 사람의 ID를 체크한다. 그룹 채팅에서 allowlist된 방의 아무나 메시지를 넣을 수 있는 상황을 방지하기 위해서다.
명시적 활성화 — .mcp.json에 채널 서버를 등록해도 --channels 플래그로 명시적으로 활성화하지 않으면 이벤트가 도착하지 않는다. 실수로 채널이 열리는 일을 막는다.
권한 프롬프트 — Claude가 작업 중 파일 삭제나 코드 실행 같은 권한이 필요한 동작을 만나면 세션이 일시정지된다. 터미널에서 직접 승인해야 한다. 부재 중에 자동 실행이 필요하면 --dangerously-skip-permissions를 사용할 수 있지만, 신뢰할 수 있는 환경에서만 써야 한다.
상시 실행: 세션 스코프의 한계와 데몬화
여기까지 설정하면 Telegram 채널이 동작한다. 하지만 기본적으로 채널은 세션에 종속된다. 터미널을 닫거나 claude 프로세스가 종료되면 채널도 함께 사라진다. 자리를 비운 사이에 Telegram 메시지가 와도 받을 수 없다.
24시간 돌아가는 Telegram 봇을 원한다면 Claude Code를 시스템 서비스로 데몬화해야 한다. 그런데 여기서 예상 밖의 문제를 만났다.
Claude Code는 TTY가 없는 환경에서 --print 모드로 빠진다. launchd나 systemd 같은 서비스 매니저는 TTY를 제공하지 않으므로, 그냥 실행하면 Input must be provided 에러가 발생한다. (인터랙티브 세션이 아니라 일회성 프롬프트 모드로 인식해서, stdin에서 프롬프트 입력을 기다리다가 없으면 종료되는 것이다.)
첫 번째로 시도한 건 script -q /dev/null로 pseudo-TTY를 만드는 것이었는데, 이것도 MCP의 stdio 통신을 깨뜨려서 채널 플러그인이 연결되지 않았다.
TTY(Teletypewriter)는 Unix/Linux에서 가장 근본적인 개념 중 하나다. 이름의 유래는 1800년대 전신기(telegraph)에서 발전한 텔레타이프 — 전기 신호로 원격지에 문자를 찍어내는 기계 — 에서 왔다. Unix가 이 물리 장치를 소프트웨어로 추상화한 것이 TTY이고, 오늘날 터미널 앱을 열 때마다 하나씩 할당된다. 서비스 매니저가 백그라운드로 프로세스를 실행하면 이 TTY가 없다.
script -q /dev/null은 가짜 TTY(pseudo-TTY)를 만들어주는 유닉스 트릭인데, 이 과정에서 stdin/stdout을 자체적으로 감싸버려서 MCP 플러그인이 Claude Code와 stdio로 통신하는 경로가 끊어진다.
결국 찾은 해결책은 tmux detached session이었다. tmux는 진짜 TTY를 제공하면서 stdio 파이프에 영향을 주지 않는다. 공식 문서에는 없는 방법이지만, 실제로 안정적으로 동작한다.
tmux(Terminal Multiplexer)는 하나의 터미널 안에서 여러 세션을 만들고, 분리(detach)했다가 다시 붙일(attach) 수 있는 도구다. SSH 접속이 끊어져도 세션이 살아있는 것으로 유명하다. 여기서 핵심은 detached 모드(
-d)로 실행하면 화면에 표시하지 않으면서도 내부적으로 진짜 TTY를 할당한다는 점이다.script와 달리 stdin/stdout을 감싸지 않아서 MCP의 stdio 통신이 정상 동작한다.
구조는 이렇다:
launchd → caffeinate → launcher.sh → tmux new-session -d → claude --channels
└── while tmux has-session (감시 루프)Launcher 스크립트
tmux 세션을 만들고, 세션이 살아있는 동안 대기하는 스크립트다. tmux 세션이 종료되면 스크립트도 끝나고, launchd의 KeepAlive가 재시작한다.
KeepAlive는 macOS launchd(서비스 매니저)의 자동 복구 옵션이다. 관리 대상 프로세스가 어떤 이유로든 종료되면 자동으로 다시 실행해준다. Linux systemd의
Restart=always와 같은 역할이다. 컨테이너 오케스트레이션의 헬스체크 + 재시작 정책을 OS 레벨에서 제공하는 셈이다.
#!/bin/bash
SESSION="claude-telegram"
TMUX=/opt/homebrew/bin/tmux
CLAUDE=$HOME/.local/bin/claude
# 이전 세션 정리
$TMUX kill-session -t "$SESSION" 2>/dev/null
# detached tmux 세션에서 Claude 실행
$TMUX new-session -d -s "$SESSION" \
"$CLAUDE" --dangerously-skip-permissions \
--channels plugin:telegram@claude-plugins-official
# 세션 종료 시 wait-for 채널에 시그널 발송하도록 hook 등록
$TMUX set-hook -t "$SESSION" session-closed \
"run-shell '$TMUX wait-for -S ${SESSION}-done'"
# 시그널 올 때까지 블로킹 대기 (폴링 없이 CPU 사용 0)
if $TMUX has-session -t "$SESSION" 2>/dev/null; then
$TMUX wait-for "${SESSION}-done"
fitmux wait-for는 지정한 채널에 시그널이 올 때까지 프로세스를 블로킹한다. session-closed hook이 세션 종료 시 시그널을 보내므로, 기존의 while+sleep 5 폴링 대비 불필요한 깨어남 없이 즉시 종료를 감지한다. has-session 체크는 hook 등록 전에 세션이 이미 종료된 경우의 fallback이다.
--dangerously-skip-permissions는 부재 중 권한 프롬프트에서 세션이 멈추는 것을 방지한다. 개인 환경에서만 사용해야 한다.
주의: 이 데몬 구성은 macOS의 launchd와 tmux에 대한 기본적인 이해가 필요하다. Linux에서는 systemd unit으로 대체할 수 있지만, 핵심 개념(detached session + 서비스 매니저)은 동일하다.
launchd plist (macOS)
앞서 만든 launcher 스크립트를 macOS가 부팅 시 자동으로 실행하고, 크래시 시 재시작하도록 등록하는 설정 파일이다. ~/Library/LaunchAgents/에 XML 형식의 plist를 두면 launchd가 관리 대상으로 인식한다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.claude-telegram</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/caffeinate</string>
<string>-s</string>
<string>/path/to/claude-telegram-launcher.sh</string> <!-- TODO: launcher.sh의 실제 경로로 변경 -->
</array>
<key>WorkingDirectory</key>
<string>/Users/yourname</string> <!-- TODO: 실제 사용자 경로로 변경 -->
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>ThrottleInterval</key>
<integer>10</integer>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/Users/yourname/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string> <!-- TODO: 실제 사용자 경로로 변경 -->
<key>HOME</key>
<string>/Users/yourname</string> <!-- TODO: 실제 사용자 경로로 변경 -->
</dict>
<key>StandardOutPath</key>
<string>/Users/yourname/Library/Logs/claude-telegram/claude-telegram.log</string> <!-- TODO: 실제 사용자 경로로 변경 -->
<key>StandardErrorPath</key>
<string>/Users/yourname/Library/Logs/claude-telegram/claude-telegram.error.log</string> <!-- TODO: 실제 사용자 경로로 변경 -->
</dict>
</plist>핵심 설정을 짚어보면:
caffeinate -s: macOS가 잠자기 모드로 들어가는 것을 방지한다.-s는 시스템 sleep 방지 플래그다.KeepAlive: true: 프로세스가 종료되면 자동 재시작한다. Claude Code가 크래시하거나 tmux 세션이 끊어져도 복구된다.ThrottleInterval: 10: 재시작 간격을 10초로 설정한다. 빠른 재시작 루프를 방지하면서도 합리적인 복구 시간을 보장한다.
서비스 관리
# 시작
launchctl load ~/Library/LaunchAgents/com.claude-telegram.plist
# 중지
launchctl unload ~/Library/LaunchAgents/com.claude-telegram.plist
# 재시작
launchctl kickstart -k gui/$(id -u)/com.claude-telegram
# 상태 확인
launchctl list | grep claude-telegram
# tmux 세션에 직접 접속 (디버깅용)
tmux attach -t claude-telegramtmux attach로 실행 중인 세션에 들어가면 Claude Code의 실제 화면을 볼 수 있다. 디버깅이 필요할 때 유용하다. Ctrl-b d로 detach하면 세션은 계속 돌아간다.
Linux에서는 같은 원리로 systemd unit + tmux 조합을 쓸 수 있다. Type=simple로 launcher 스크립트를 실행하고, Restart=always로 재시작 정책을 설정하면 된다.
그래서 데몬화까지 해서 뭘 하나 — Scheduled Tasks
Telegram 채널이 24시간 떠 있으면, 단순히 메시지를 주고받는 것 이상이 가능해진다. Claude Code에는 세션 내에서 프롬프트를 주기적으로 실행하는 Scheduled Tasks 기능이 있다(v2.1.72). 상시 실행 중인 채널과 이 스케줄 기능을 조합하면, 자리를 비운 사이에도 Claude가 알아서 모니터링하고 결과를 Telegram으로 보내주는 구조가 된다.
/loop 명령으로 간단히 설정할 수 있다.
# 5분마다 배포 상태 확인
/loop 5m gh run view --json status,conclusion 확인하고, 완료되면 Telegram으로 알려줘
# 자연어로 일회성 리마인더
45분 후에 integration test 결과 확인하고 Telegram으로 보내줘
# 다른 slash command를 주기적으로 실행
/loop 20m /review-pr 1234채널이 “외부에서 세션으로 push”하는 구조라면, Scheduled Tasks는 “세션이 스스로 poll”하는 구조다. 이 둘을 조합하면 다양한 시나리오가 가능해진다.
배포 모니터링 — Telegram에서 배포를 시작한 뒤, /loop으로 GitHub Actions 상태를 주기적으로 확인한다. 완료되면 Telegram으로 결과를 알려준다.
/loop 2m gh run view --json status,conclusion --jq '.status' 확인하고, 완료되면 Telegram으로 결과 알려줘PR 리뷰 감시 — 30분마다 PR의 새 코멘트를 확인하고, 변경이 있으면 요약해서 Telegram으로 보낸다.
/loop 30m PR #42의 리뷰 코멘트 새로 달린 게 있으면 Telegram으로 요약해서 보내줘서비스 헬스체크 — 로컬에서 실행 중인 서비스의 로그를 주기적으로 확인한다.
/loop 10m tail -5 ~/my-service/logs/app.log 확인해서 ERROR가 있으면 Telegram으로 알려줘스케줄 태스크는 세션 스코프라는 점을 기억해야 한다. 세션을 종료하면 모든 태스크가 사라지고, 반복 태스크는 최대 3일 후 자동 만료된다. 세션당 최대 50개의 스케줄을 동시에 등록할 수 있으며, Claude가 다른 응답을 생성하는 중이면 idle 상태가 될 때까지 대기한다(missed fire catch-up은 없다). 영구적인 스케줄링이 필요하면 GitHub Actions의 schedule 트리거나 별도 서비스를 써야 한다.
내부적으로는 CronCreate(cron 표현식으로 태스크 생성), CronList(목록 조회), CronDelete(ID로 삭제) 세 가지 도구가 사용된다. CLAUDE_CODE_DISABLE_CRON=1 환경변수로 기능 자체를 비활성화할 수도 있다.
| 구분 | Channels | Scheduled Tasks |
|---|---|---|
| 트리거 | 외부 이벤트 (메시지, 알림) | 시간 기반 (cron) |
| 방향 | 외부 → 세션 (push) | 세션 내부 (poll) |
| 용도 | 채팅, CI 결과 수신 | 배포 모니터링, 리마인더 |
| 상호보완 | CI가 실패를 push | 주기적으로 상태 poll |
More: 커스텀 채널 빌드
주의: 커스텀 채널은 현재 Research Preview 단계에서 실제로 사용할 수 없다.
--channels플래그는 Anthropic allowlist에 등록된 플러그인(Telegram, Discord, fakechat)만 지원하며, 직접 만든 채널을 연결하려면--dangerously-load-development-channels플래그가 필요하다. 아래 내용은 channels-reference 문서에 기반한 프로토콜 구조 설명이며, GA 이후 커스텀 채널이 정식 지원되면 활용 가능한 방향을 제시하는 것이다.
Telegram과 Discord 외에 자체 채널을 만들 수도 있다. 채널의 실체는 MCP 서버다. 로컬에서 HTTP 요청을 받아 Claude Code 세션에 이벤트로 전달하는 구조다.
출처: Claude Code Channels Reference
구현에 필요한 것은 세 가지다.
claude/channelcapability 선언 — 이것이 있어야 Claude Code가 이벤트 리스너를 등록한다.notifications/claude/channel이벤트 발행 — 외부 이벤트를 세션에 전달한다.- stdio transport 연결 — Claude Code가 서버를 subprocess로 실행하고 stdin/stdout으로 통신한다.
// requires: @modelcontextprotocol/sdk, bun runtime
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
const mcp = new Server(
{ name: 'my-channel', version: '0.0.1' },
{
capabilities: {
experimental: { 'claude/channel': {} }, // 채널로 인식
tools: {}, // 양방향이면 추가
},
instructions: '이벤트 도착 시 어떻게 처리하고 응답할지 Claude에 알려주는 텍스트',
},
)
// 이벤트 발행 — Claude에 <channel> 태그로 전달된다
await mcp.notification({
method: 'notifications/claude/channel',
params: {
content: 'build failed on main',
meta: { severity: 'high', run_id: '1234' },
},
})
await mcp.connect(new StdioServerTransport())이 프로토콜 구조를 응용하면 다양한 채널을 구상할 수 있다. 로컬에 Bun.serve()로 HTTP 서버를 띄우고, GitHub Actions CI 실패 webhook이나 Grafana 알림을 받아 mcp.notification()으로 변환해서 세션에 전달하는 구조다. Claude가 실패 로그를 분석하고 수정 PR을 자동 생성하거나, kubectl logs로 즉시 원인을 분석하는 워크플로우가 가능하다. 공인 IP가 없다면 Cloudflare Tunnel이나 ngrok으로 로컬 서버를 노출할 수 있다. 구체적인 webhook payload 파싱과 서버 구현은 channels-reference 문서를 참고한다.
커스텀 채널을 만들 때 보안도 직접 구현해야 한다. mcp.notification()을 호출하기 전에 발신자 allowlist를 체크하는 것이 필수다. 체크 기준은 발신자 ID이지 채팅방 ID가 아니다. 그룹 채팅에서 허가되지 않은 사용자가 프롬프트를 주입하는 것을 막기 위해서다.
단방향과 양방향
채널은 용도에 따라 단방향과 양방향 중 선택할 수 있다.
| 타입 | 용도 | reply tool |
|---|---|---|
| 단방향 | CI 알림, 모니터링 이벤트 수신 | 불필요 |
| 양방향 | 채팅 브릿지, 응답이 필요한 알림 | ListToolsRequestSchema + CallToolRequestSchema 핸들러 추가 |
CI 실패 알림처럼 Claude가 받아서 처리만 하면 되는 경우에는 단방향으로 충분하다. Telegram처럼 응답을 돌려보내야 하는 경우에는 양방향으로 구현한다.
비교: Remote Control과의 차이
Channels와 비슷해 보이지만 근본적으로 다른 기능이 Remote Control이다. 혼동하기 쉬우므로 차이를 짚어둔다.
Remote Control은 로컬에서 실행 중인 Claude Code 세션을 claude.ai/code나 Claude 모바일 앱에서 그대로 이어서 사용하는 기능이다. 세 가지 방법으로 활성화할 수 있다.
# 서버 모드 (터미널에서 대기, 최대 32세션)
claude remote-control
# 인터랙티브 세션 + Remote Control
claude --remote-control
# 기존 세션에서 활성화
/remote-controlQR 코드가 표시되면 spacebar를 눌러 모바일 앱에서 스캔할 수 있다. 모든 세션에서 자동 활성화하려면 /config에서 설정하면 된다.
핵심 차이는 이렇다. Channels는 메시지 브릿지이고, Remote Control은 세션 미러링이다.
| 항목 | Channels (Telegram) | Remote Control |
|---|---|---|
| 목적 | 외부 플랫폼 이벤트를 세션에 푸시 | 로컬 세션을 다른 기기에서 직접 조작 |
| 인터페이스 | Telegram, Discord 앱 | claude.ai/code 또는 Claude 앱 |
| 프로토콜 | MCP 플러그인 (polling) | Anthropic API (outbound HTTPS) |
| 양방향성 | reply tool로 응답 전송 | 동일 세션의 full 인터랙션 |
| 도입 버전 | v2.1.80 | v2.1.51 |
| 동시 접속 | 여러 채널 플러그인 병렬 가능 | 프로세스당 1세션 (서버 모드 최대 32) |
Channels는 Telegram이라는 외부 플랫폼에서 메시지를 보내면 Claude Code 세션이 받아서 처리하고, reply tool로 응답을 돌려보낸다. 터미널에서는 도구 호출과 “sent” 확인만 보인다. Remote Control은 세션 자체를 브라우저나 앱에 그대로 보여준다. 동일한 대화 내역, 동일한 도구 호출 화면이 모든 연결된 기기에서 동기화된다.
두 기능은 함께 쓸 수 있다. Remote Control로 세션을 열어두고, Telegram 채널로 CI 알림이나 모니터링 이벤트를 받는 구성이 가능하다.
정리와 제약
Channels, Scheduled Tasks, Remote Control 세 가지를 조합하면 Claude Code 세션의 활용 범위가 크게 넓어진다.
- Channels — 외부 이벤트 수신. Telegram 메시지, CI 실패 알림, 모니터링 경고를 세션에 push
- Scheduled Tasks — 주기적 폴링. 배포 상태 확인, PR 리뷰 감시, 일회성 리마인더
- Remote Control — 세션 미러링. 모바일이나 다른 브라우저에서 세션을 직접 조작
다만 현재 제약도 명확하다.
- Research Preview 단계 — 정식 기능이 아니다.
--channels플래그 문법과 프로토콜 계약이 변경될 수 있다. 커스텀 채널은--dangerously-load-development-channels플래그가 필요하고, 공식 마켓플레이스 등록에는 보안 리뷰가 필요하다. - claude.ai 로그인 필수 — Console이나 API 키 인증으로는 동작하지 않는다. Team/Enterprise 조직은 관리자가
channelsEnabled를 명시적으로 활성화해야 한다. - 세션 스코프 — 채널과 스케줄 태스크 모두 세션이 종료되면 사라진다. 앞서 다룬 launchd + tmux 데몬화로 상시 실행은 가능하지만, 스케줄 태스크는 최대 3일 후 자동 만료된다.
- 허용 플러그인 제한 — 현재 Anthropic이 관리하는 allowlist에 있는 플러그인(Telegram, Discord, fakechat)만
--channels로 사용할 수 있다. - 비용과 컨텍스트 관리 — 상시 실행 데몬과
/loop태스크는 지속적으로 토큰을 소모한다. 컨텍스트 윈도우가 차면 이전 대화가 요약되면서 맥락이 유실될 수 있다. 모니터링 같은 단순 작업에는--model haiku옵션으로 비용을 절감할 수 있다. Bun 런타임과 Claude Code 프로세스가 상시 점유하는 시스템 리소스(RAM ~200-500MB)도 고려해야 한다.
Channels가 GA되고 커스텀 채널이 마켓플레이스에서 자유롭게 설치 가능해지면, CI/CD, 모니터링, 채팅 플랫폼을 Claude Code 세션에 직접 연결하는 것이 일반적인 워크플로우가 될 수 있다. Channels는 아직 Research Preview이지만, MCP 프로토콜 위에 외부 이벤트를 세션에 연결하는 구조는 이미 실용적이다. 이 글에서 다룬 설정으로 먼저 시작하고, GA 이후의 확장에 대비하면 된다.
에피소드: Polling 충돌 — enabledPlugins의 User 레벨 함정
데몬화까지 마치고 잘 돌아가던 어느 날, Telegram 메시지를 보냈는데 응답이 오지 않았다. tmux 세션에 들어가보니 Listening for channel messages 상태인데 메시지가 도착하지 않는다.
원인은 다른 Claude Code 세션이었다. 같은 머신에서 작업용으로 새 세션을 열었는데, 그 세션에서도 Telegram 플러그인의 bun 프로세스가 떠서 같은 봇으로 polling을 시작한 것이다. Telegram Bot API는 하나의 getUpdates 연결만 허용한다. 나중에 연결한 세션이 기존 연결을 밀어내면서, 데몬 세션이 메시지를 받지 못하게 됐다.
문제의 근본은 enabledPlugins 설정이 user 레벨(~/.claude/settings.json)에 있었다는 점이다. user 레벨 설정은 어떤 디렉토리에서 Claude Code를 열든 적용된다. --channels 플래그 없이 실행해도 MCP 서버로서 플러그인이 로드되면서 polling이 시작된다.
// ~/.claude/settings.json (user 레벨) — 모든 세션에서 telegram polling 시작
{
"enabledPlugins": {
"telegram@claude-plugins-official": true // 이게 문제
}
}첫 번째 시도는 user 레벨에서 telegram을 빼는 것이었다. 그런데 --channels plugin:telegram@claude-plugins-official으로 실행해도 플러그인이 로드되지 않았다. --channels 플래그는 enabledPlugins에 등록된 플러그인만 채널로 활성화한다. 설정에 없으면 찾지도 않는다.
해결책은 전용 디렉토리를 분리하는 것이었다.
~/claude-telegram/ # 데몬 전용 디렉토리
└── .claude/
└── settings.local.json # project 레벨에서만 telegram 활성화
~/.claude/settings.json # user 레벨에서 telegram 제거// ~/claude-telegram/.claude/settings.local.json (project 레벨)
{
"enabledPlugins": {
"telegram@claude-plugins-official": true
}
}launcher 스크립트에 -c "$WORKDIR" 한 줄 추가해서 tmux 세션의 작업 디렉토리를 ~/claude-telegram으로 지정한다.
WORKDIR=$HOME/claude-telegram
$TMUX new-session -d -s "$SESSION" -c "$WORKDIR" \
"$CLAUDE" --dangerously-skip-permissions \
--channels plugin:telegram@claude-plugins-official이제 ~/claude-telegram/에서 실행되는 데몬 세션만 telegram 플러그인을 로드하고, 다른 디렉토리에서 여는 작업 세션에서는 polling이 시작되지 않는다. user 레벨 ~/.claude/CLAUDE.md의 공통 규칙은 데몬 세션에서도 그대로 적용되므로, 프로젝트 컨텍스트 없이도 리모트 컨트롤 용도로는 충분하다.
교훈은 명확하다. Telegram 같은 polling 기반 채널 플러그인은 반드시 project 레벨(settings.local.json)에서 활성화하고, user 레벨에는 두지 않는다. 그렇지 않으면 새 세션을 열 때마다 기존 데몬의 연결을 끊어버린다.
참고 문서:
- Channels — 채널 개념, 설정, 보안
- Channels Reference — 커스텀 채널 빌드 프로토콜
- Scheduled Tasks — /loop, cron 도구
- Remote Control — 세션 원격 접속
- Telegram Plugin Source — 공식 Telegram 플러그인 코드