Claude Code의 /loop: '고정 간격'과 '셀프 페이싱' 사용 구분 방법 — launchd 17 작업 운영 경험을 바탕으로 한 구현
요약
본 글은 Claude Code의 `/loop` 명령어에 대한 실무 경험을 바탕으로, '고정 간격'과 '셀프 페이싱' 두 가지 모드의 사용 기준과 차이점을 설명합니다. 특히 주기적인 대기(polling) 작업 시 어떤 방식을 선택해야 효율적이며, 실제 운영 환경에서의 구현 팁을 공유합니다.
핵심 포인트
- 주기가 정해진 감시 대상은 '고정 간격'을 사용하세요.
- 셀프 페이싱은 주기가 불규칙하거나 예측하기 어려울 때 적합합니다.
- /loop는 세션 유지 동안만 작동하므로 주의해야 합니다.
- 폴링 간격은 실제 데이터 업데이트 빈도에 맞춰 설정하는 것이 중요합니다.
당사(合同会社ジョインクラス)는 기사 공개, SNS 배포, 매출 집계 등의 업무를 launchd의 17개 작업과 Claude Code로 처리하고 있습니다. 자동화율은 약 98%입니다.
이러한 운영 과정에서 가장 고민했던 부분은 '대기(待つ処理)' 코드 작성 방법이었습니다. 배포가 끝날 때까지 기다리거나, PR의 CI가 통과할 때까지 지켜보거나, 레이트 리밋(rate limit)이 풀리면 재실행하는 등의 처리가 해당됩니다.
Claude Code의 /loop에는 두 가지 모드가 있습니다.
- 고정 간격 (固定間隔):
/loop 5m /foo와 같이 간격을 지정합니다. - 셀프 페이싱 (セルフペーシング): 간격을 생략합니다. 모델이 매번
ScheduleWakeup으로 다음 기상 시간을 결정합니다.
본문에서는 제가 실제로 사용하고 있는 스크립트를 기반으로, 두 모드의 사용 기준, 절차, 함정, 검증 방법을 정리했습니다.
| 상황 | 권장 방식 | 이유 |
|---|---|---|
| 감시 대상의 주기가 정해져 있음 (매시간 집계, 5분 간격 헬스 체크) | 고정 간격 | 매번 동일한 처리입니다. 모델에 간격을 판단하게 할 의미가 없습니다. |
| ... | /loop 대신 launchd 또는 /schedule 사용 권장 | |
/loop는 세션이 살아있는 동안만 작동합니다. |
세 번째 항목은 특히 놓치기 쉽습니다. run_in_background로 시작한 명령어는 끝나면 에이전트가 자동으로 호출됩니다. 여기에 60초 간격의 폴링(polling)을 추가해도 토큰만 줄어들 뿐입니다.
당사의 작업 그룹은 모든 처리에서 common.sh를 source 합니다. 아래는 거기서 발췌한 부분입니다 (실제 파일은 .company/scripts/common.sh).
#!/bin/bash
set -euo pipefail
export PATH="/Users/kyoagun/.nvm/versions/node/v22.17.0/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
...
작업 실패는 여기서 Slack으로 통지됩니다. 다만 작업 중인 세션에서는 '로그의 끝에 ERROR가 나오는지'를 일정한 간격으로 확인하는 것이 더 빨리 알아챌 수 있습니다. 이처럼 매번 동일한 체크는 고정 간격으로 충분합니다.
/loop 10m .company/scripts/logs/ 디렉토리에서 최근 10분간 업데이트된 *.log의 끝 30줄을 확인하고, ERROR 또는 종료 코드가 나오면 원인과 대상 스크립트 이름을 한 줄로 보고합니다. 아무것도 없으면 '이상 없음'만 반환합니다.
포인트는 다음 세 가지입니다.
- 출력을 짧게 지정: '아무것도 없으면 1줄로'라고 명시하지 않으면, 매번 긴 리포트가 돌아와 화면이 채워집니다. -
- 간격은 로그 업데이트 빈도에 맞추기: 당사의 작업은 최소 수십 분 간격이므로, 1m으로 해도 새로운 정보는 거의 없습니다. -
- 중지할 때는 명시하기: 세션을 닫으면 멈추지만, 계속 유지한 채 멈추고 싶을 때는 '루프를 멈춰'라고 전달합니다.
Zenn에서는 'push는 성공했는데 기사가 공개되지 않는' 경우가 있습니다 (레이트 리밋에 걸린 경우입니다). 당사에서는 다음 스크립트로 재배포하고 있습니다 (실제 파일은 .company/scripts/retry-zenn-deploy.sh).
#!/bin/bash
# Zenn 배포 실패 기사의 재배포 스크립트
set -uo pipefail
...
push 이후에 공개되었는지 확인하는 처리는 반영까지 수십 초 만에 끝날 수도 있고, 10분 이상 걸릴 수도 있습니다. 고정 5분은 너무 빨라서 공염방이 계속 되거나, 느려서 실패 감지가 지연됩니다. 여기서는 셀프 페이싱을 사용합니다.
/loop https://zenn.dev/joinclass/articles/claude-code-morning-dashboard 를 curl -s -o /dev/null -w '%{http_code}'로 확인합니다. 200이면 루프를 멈추고 보고합니다. 404가 30분 이상 지속되면 retry-zenn-deploy.sh를 단 한 번 실행한 후, 그 이후는 간격을 넓혀서 계속 확인을 진행합니다.
셀프 페이싱에서는 모델이 매번 ScheduleWakeup을 호출하여 다음 기상 시간을 결정합니다. 예를 들어 다음과 같이 상황에 맞춰 간격이 변해갑니다 (이미지입니다).
delaySeconds: 240 reason: "푸시 직후. Zenn의 일반적인 반영 시간에 맞춰 4분 후에 한 번 확인"
delaySeconds: 600 reason: "아직 404. 레이트 리밋일 가능성이 있으니 간격을 넓히자"
stop: true
짧은 간격으로 폴링하지 않기: 완료를 파악하고 있는 처리(백그라운드 Bash 등)는 끝나면 자동으로 재시작됩니다. 이 경우에는 폴링하지 않고 1200초 이상의 폴백만 설정합니다 -
종료 조건을 프롬프트에 쓰기: '200이 되면 멈춘다'와 같은 조건이 없으면 루프가 계속되어 멈추지 않습니다 -
부작용이 있는 처리는 횟수를 제한하기: '한 번만'이라고 쓰지 않으면 재배포가 반복되어 Zenn의 레이트 리밋(1일 1건)에 또 걸립니다. 저희는 실제로 여러 번 걸린 경험이 있어, 그 결과 위의 락 파일 방식을 만들었습니다 -
지연 범위는 60~3600초: 1시간 이상 기다리는 처리는 /loop
방식에는 적합하지 않습니다. launchd를 사용합니다.
저는 예전에 macOS의 cron을 그대로 사용하고 있었습니다. 그런데 전체 디스크 접근 권한이 없어서, cron 작업이 모두 동작하지 않은 적이 있습니다. 그래서 launchd로 전면 이행했습니다. 매일 정해진 시간에 동작하는 처리나, 세션을 닫아도 계속 이어가고 싶은 처리는 지금도 전부 launchd입니다.
# 등록된 job과 직전 종료 코드를 확인 (2열지가 0이 아니면 실패)
launchctl list | grep com.joinclass
# 1회성 job이 자신을 삭제했는지
...
사용법을 정리하면 다음과 같습니다.
launchd: 매일 정해진 시간에 동작하는 처리, 영속성이 필요한 처리 -
: 작업 중만, 일정한 주기로 같은 체크를 돌리고 싶을 때 /loop
고정 간격 -
: 끝나는 시간이 알 수 없는 외부 상태를, 작업 중에 지켜볼 때 /loop
세ルフペーシング(Self-Pacing)
- 자신의 자동화에서 '기다리는 처리' 하나를 고릅니다 (CI, 배포, 빌드 등)
- 그 처리의 끝나는 시간이 정해져 있는지, 매번 달라지는지 확인합니다
- 정해져 있다면
/loop 5m ... - 달라진다면 간격을 생략하고 실행합니다 - 프롬프트에 종료 조건・출력량・부작용의 횟수 상한 세 가지를 작성합니다 - 몇 번 동작했는지 확인하면
reason을 보고, 간격 판단이 적절한지 확인합니다.
저희가 처음에 만든 자동화는, 간격을 전부 고정으로 했습니다. 그 결과, 공회전 체크에 불필요한 토큰을 사용했습니다. '상태가 어느 정도의 속도로 변하는가'로 모드를 선택하도록 한 이후로는, 감시 비용과 탐지 지연 둘 다 줄었습니다.
launchd・Hooks・/loop
를 조합한 자동화의 전체 그림은, 저서 『Claude Code 전자동화 바이블』에서 설명하고 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기