2 minute read

🇺🇸 Read this post in English

econ-radar 자동화 글에서 매일 자동으로 뉴스레터가 만들어지고 블로그에 올라간다고 했습니다. 텔레그램 채널(@econradar)에도 알림이 간다고 했습니다.

며칠이 지나도록 텔레그램 알림이 오지 않았습니다.

처음엔 토큰 문제인 줄 알았습니다

CCR(Claude Code Routine) 프롬프트에 텔레그램 발송 코드를 넣었습니다. 토큰을 넣고, 채팅 ID를 넣고, sendMessage API를 호출하게 했습니다. 로컬에서는 잘 됐습니다.

클라우드에서는 연결 자체가 안 됐습니다. 토큰을 다시 확인했습니다. 채팅 ID도 다시 확인했습니다. 같았습니다.

며칠 더 보다가 원인을 찾았습니다. CCR이 실행되는 Anthropic 클라우드 샌드박스 환경은 api.telegram.org로 나가는 아웃바운드 HTTPS가 차단돼 있었습니다. 설정 문제가 아니라 설계상 제약이었습니다.

해결 방향: 에이전트가 파일만 쓰고, 발송은 다른 곳에서

직접 부를 수 없다면, 인터넷 제한이 없는 곳에 맡기면 됩니다. GitHub Actions는 일반 인터넷 환경에서 실행됩니다.

구조는 간단합니다.

CCR (클라우드 에이전트)
  → 발송할 내용을 vault/push/YYYY-MM-DD.md 에 저장
  → git push origin main

GitHub Actions (인터넷 제한 없음)
  → vault/push/*.md 파일 변경 감지
  → 텔레그램 API 호출 → 채널 발송

에이전트는 파일을 쓰고 push만 합니다. 텔레그램 발송은 Actions가 맡습니다. git push가 이벤트 브리지 역할을 하는 구조입니다.

구현

발송 스크립트 (send_push.py)

push 파일을 읽어 텔레그램으로 보내는 스크립트입니다. 환경변수에서만 토큰을 읽습니다.

import os, sys, requests

BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
CHAT_ID   = os.environ.get("TELEGRAM_CHAT_ID", "")

if not BOT_TOKEN or not CHAT_ID:
    print("[ERROR] 환경변수 미설정")
    sys.exit(1)

date = sys.argv[1]
text = open(f"vault/push/{date}.md").read()

requests.post(
    f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
    json={"chat_id": CHAT_ID, "text": text, "parse_mode": "Markdown"}
)

GitHub Actions 워크플로

vault/push/ 아래 파일이 바뀔 때만 실행됩니다.

on:
  push:
    branches: [main]
    paths:
      - 'vault/push/*.md'

jobs:
  send:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: 날짜 감지
        id: detect
        run: |
          BEFORE="$"
          if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then
            BEFORE="HEAD~1"
          fi
          DATE=$(git diff --name-only "$BEFORE" HEAD \
            | grep -E '^vault/push/[0-9]{4}-[0-9]{2}-[0-9]{2}\.md$' \
            | sed 's|vault/push/||; s|\.md||' \
            | sort -r | head -1)
          echo "date=$DATE" >> "$GITHUB_OUTPUT"

      - name: 텔레그램 발송
        if: steps.detect.outputs.date != ''
        env:
          TELEGRAM_BOT_TOKEN: $
          TELEGRAM_CHAT_ID: $
        run: python3 scripts/send_push.py $

GitHub Secrets에 TELEGRAM_BOT_TOKENTELEGRAM_CHAT_ID를 등록하면 완성입니다.

작동하고 나서 발견한 두 번째 버그

첫날은 잘 됐습니다. 며칠 뒤 또 알림이 안 왔습니다.

이번에는 다른 이유였습니다. CCR이 한 번의 git push에 커밋을 여러 개 묶어서 보냈습니다.

b38e97f  econ-radar: 2026-06-16 자동 생성  ← vault/push 파일이 여기에
8834dde  topics MOC 갱신
027155e  반도체 topics MOC 갱신
4520dc9  topics MOC 최종 정합              ← HEAD

처음에 작성한 감지 로직이 HEAD~1 HEAD였습니다. 마지막 커밋 하나만 비교하는 방식입니다. 4520dc9 vs 027155e만 봤으니 vault/push/ 변경을 놓쳤습니다. 워크플로는 “success”로 끝났지만 발송 step은 조용히 skip됐습니다.

수정은 간단했습니다. HEAD~1 HEAD 대신 github.event.before..HEAD를 쓰면 해당 push에 포함된 모든 커밋을 통째로 비교합니다. fetch-depth: 0으로 전체 히스토리를 내려받아야 합니다.

처음부터 이렇게 짰으면 됐는데, 단일 커밋 push로 테스트했던 게 문제였습니다. 에이전트가 실제 운영에서 어떻게 commit을 구성하는지 먼저 확인했어야 했습니다.

채널 chat_id 찾는 법

새 채널을 만들고 chat_id를 찾는 과정도 예상 밖이었습니다. 흔히 쓰는 getUpdates API가 채널에서는 빈 배열을 반환했습니다.

getChat을 쓰면 됩니다.

https://api.telegram.org/bot{TOKEN}/getChat?chat_id=@채널명

result.id 필드가 chat_id입니다. 채널은 -100으로 시작하는 음수가 나옵니다.

이 패턴이 쓸 곳

텔레그램에만 국한된 이야기가 아닙니다. CCR 환경에서 외부 API를 직접 부를 수 없는 경우라면 어디든 같은 구조로 풀 수 있습니다.

  • Slack: vault/push/slack-YYYY-MM-DD.md + slackapi/slack-github-action
  • Discord, Notion, 사내 API — 모두 동일 패턴

에이전트가 파일을 쓰면 Actions가 읽는다. 이 한 문장으로 요약됩니다.

Updated: