본문으로 건너뛰기
wooncloud

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

claude -p 를 cron 이나 Node 스케줄러로 직접 호출하는 방식으로, 사내망 접근·데이터 통제엔 유리하지만 cron 의 PATH·인증 누락과 토큰 폭주, 프롬프트 인젝션을 직접 막아야 합니다.

·8 min read·— views··

정의

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 은 로그인 셸 환경을 상속하지 않으니 PATH 와 인증을 명시해야 합니다. 이게 첫 시도에서 가장 자주 깨지는 지점입니다.

인증이 꼭 API 키일 필요는 없습니다. 구독을 쓴다면 claude setup-token 으로 비대화형용 장기 OAuth 토큰을 발급받아 CLAUDE_CODE_OAUTH_TOKEN 으로 넘기는 쪽이 낫습니다. 어느 쪽이든 crontab 파일에 평문으로 박지 마세요 — crontab 은 백업·dotfiles·crontab -l 로 쉽게 새어 나갑니다. 권한을 좁힌 별도 파일에 두고 래퍼 스크립트에서 읽어 들이는 편이 안전합니다.

한 단계 위: Node 스케줄러

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

import schedule from 'node-schedule';
import { execFile } from 'child_process';
import { promisify } from 'util';
const run = promisify(execFile);
 
schedule.scheduleJob('0 */1 * * *', async () => {
  // execSync 는 이벤트 루프를 통째로 막는다 — 호출이 끝날 때까지 다른 잡·타이머가 전부 멈춘다
  const { stdout } = await run('claude', ['-p', '신규 이슈 트리아지', '--output-format', 'json'],
    { maxBuffer: 32 * 1024 * 1024 });
  // 결과를 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 클라우드 (fresh clone)자기 서버
운영 책임없음Anthropic본인
비용 모델구독 한도 차감구독 한도 차감API 토큰 + 인프라비
한계세션 닫히면 중단일일 실행 캡—
셋업 시간즉시분 단위시간~일 단위

/loop 을 루틴과 한 칸에 묶으면 안 됩니다. /loop 은 내 머신의 Claude Code 프로세스 안에서 도는 세션 스케줄러라, 로컬 파일과 사내망에 그대로 닿고 코드도 밖으로 나가지 않습니다. 대신 세션이 닫히면 같이 끝납니다. 클라우드로 나가는 건 루틴 쪽입니다.