본문으로 건너뛰기
wooncloud

클로드코드 자동화 : 외부 스케줄러 + 헤드리스 모드

정의 한 줄을 외부 스케줄러가 주기적으로 호출하는 구조다. 운영체제 cron, Node의 node-schedule, GitHub Actions cron, Kubernetes CronJob 어디든 붙는다. 빌트인 루틴과 달리 인프라를 직접 들고 있어야…

정의

claude -p "프롬프트" 한 줄을 외부 스케줄러가 주기적으로 호출하는 구조다. 운영체제 cron, Node의 node-schedule, GitHub Actions cron, Kubernetes CronJob 어디든 붙는다. 빌트인 루틴과 달리 인프라를 직접 들고 있어야 한다.

헤드리스 모드란

-p (또는 --print) 플래그로 Claude Code를 비대화형으로 실행하는 모드다. 터미널 세션 없이 프롬프트를 받아 한 번 실행하고 종료한다. stdin 입력, stdout 출력, JSON 포맷 지원이라 유닉스 파이프와 그대로 합쳐진다.

claude -p "어제 커밋 요약해줘"
cat error.log | claude -p "이 에러의 근본 원인을 짧게 설명"
claude -p "@src/auth.ts를 async/await로 리팩터" --output-format json

--allowedTools로 도구를 화이트리스트하고, --max-turns로 루프 길이를 제한하고, --dangerously-skip-permissions로 권한 프롬프트를 끈다.

왜 직접 짜나

빌트인(/loop, 루틴)이 있는데도 이 방식을 고르는 이유는 세 가지다.

사내망 접근. 회사 DB, 사내 API, VPN 안쪽 리소스를 건드려야 하면 클라우드 루틴은 못 쓴다. 사내 서버에서 cron이 도는 게 답이다.

데이터 통제. 코드와 로그를 외부 인프라에 올리지 않고 끝내고 싶을 때.

기존 인프라 재사용. 이미 Jenkins, Airflow, GitHub Actions, 사내 스케줄러가 있으면 거기 한 줄 추가하는 게 새 시스템 도입보다 싸다.

가장 단순한 형태: cron

# crontab -e
0 9 * * * /usr/local/bin/claude -p "어제 PR들 리뷰 요약" >> /var/log/claude-daily.log 2>&1

세 줄이면 끝난다. 다만 cron은 환경변수를 안 읽어오니 ANTHROPIC_API_KEY와 PATH를 crontab 안에 직접 박아야 한다. 이게 첫 시도에서 가장 자주 깨지는 지점이다.

한 단계 위: Node 스케줄러

node-schedule, BullMQ, Agenda를 쓰면 큐, 재시도, 동시성 제어, 분산 락이 따라온다. cron 한 줄로는 못 푸는 운영 요건이 생기면 여기로 올라간다.

import schedule from 'node-schedule';
import { execSync } from 'child_process';
 
schedule.scheduleJob('0 */1 * * *', () => {
  const out = execSync('claude -p "신규 이슈 트리아지" --output-format json');
  // 결과를 DB에 적재, Slack에 전송, 다음 잡으로 체이닝
});

ClaudeOps가 정확히 이 패턴이다. 스케줄러가 깨워서, 에이전트가 REST API로 할 일을 받아오고, 끝나면 자동 커밋한다.

출력 처리: JSON 포맷

--output-format json을 쓰면 결과·세션 ID·토큰 사용량·소요시간이 구조화된 객체로 나온다. 그대로 jq로 자르거나 다음 단계로 넘기면 된다.

claude -p "버그 찾아줘" --output-format json | jq '.result'

stream-json은 토큰을 스트리밍으로 받아 실시간 로깅·관측에 쓴다.

운영 시 유의점

비용 통제가 1순위다. 새벽 3시에 40개 PR을 도느라 토큰 폭주해도 사람이 못 본다. --max-turns로 캡, 워크플로별 토큰 한도, 주간 누적 알람 — 이 셋은 운영 시작 전에 깔아야 한다.

프롬프트 인젝션을 가정하라. PR 본문, 이슈 코멘트, 사용자 입력이 프롬프트에 들어가면 그건 untrusted다. 권한 도구를 좁히고, 위험한 명령은 사람 승인 게이트를 둔다.

컨텍스트는 매번 fresh다. 헤드리스는 세션 상태가 없다. 필요한 정보는 프롬프트나 파일로 매번 넣어야 한다. 운구름님이 ClaudeOps에서 progress 파일·git history로 인수인계하는 구조가 이걸 푸는 정석이다.

--dangerously-skip-permissions는 격리 환경에서만. 검증 끝난 프롬프트, 컨테이너나 CI 러너 안에서, 도구 화이트리스트와 함께 쓴다.

/loop·루틴과의 차이

같은 "스케줄 자동화"여도 책임 경계가 다르다.

/loop·루틴외부 스케줄러 + 헤드리스
실행 인프라Anthropic직접
사내망 접근제한자유
데이터 위치Anthropic 클라우드자기 서버
운영 책임Anthropic본인
비용 모델구독 한도 차감API 토큰 + 인프라비
셋업 시간분 단위시간~일 단위