Claude Code 무인 자율 루프로 사이드 프로젝트 완주시키기
개요
Claude Code로 코딩을 하다 보면 이런 생각이 듭니다. “이거 태스크 목록은 다 정해져 있는데, 내가 옆에서 엔터만 쳐주고 있네…?”
사이드 프로젝트는 항상 시간이 부족합니다. 퇴근 후 두어 시간으로는 태스크 30개짜리 백로그가 줄어들 기미가 안 보이죠 🥲 그래서 실험을 시작했습니다. Claude Code를 무인 루프로 돌려서, 자는 동안 프로젝트를 완주시킬 수 없을까?
결론부터 말하면 됩니다. Flutter 앱 하나에서 이 패턴을 처음 정립해 하루 만에 34개 태스크 중 23개를 자율 완료시켰고, 이후 웹 게임, API 서버 하드닝(태스크 15개 무인 완주, 테스트 78개 → 184개), watchOS 컴패니언 앱(13개 태스크 완주 후 TestFlight 출시까지 도달) 등 여섯 개 프로젝트에 이식하면서 다듬었습니다.
이 포스트에서는 그렇게 검증된 구성 요소와, 전부 직접 밟아본 실전 함정들을 정리합니다.
핵심 아이디어는 한 문장입니다.
대화가 아니라 파일이 상태를 가진다.
구성 요소
1. 원장 문서 - 단일 진실원본
WORKPLAN.md 하나에 모든 상태를 넣습니다.
- 골 정의: 검증 가능한 게이트 목록 (기능·품질·배포)
- Phase별 체크박스 태스크 - 체크박스가 곧 진행 상태
- 결정 로그 / 진행 로그
- 루프 프로토콜: 픽업 규칙, 검증 없이는 체크 금지, BLOCKED 규칙
어떤 세션이 죽어도 이 문서 + git 히스토리만 있으면 다음 세션이 이어받습니다. 대화 이력에 의존하는 순간 무인 운영은 불가능해집니다.
2. 루프 러너 스크립트
tmux 안에서 claude -p(headless)를 무한 반복하는 bash 스크립트입니다. 골격은 이렇습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PROMPT='자율 작업 반복이다. 먼저 WORKPLAN.md를 읽어라.
미완료([ ]) 태스크 중 다음 하나를 골라 docs/work-orders/<ID>.md 지시서를 읽고 그대로 구현해라.
임의 설계 판단 금지, 지시서 없으면 BLOCKED 표기.
완료 후 수용 기준의 검증 명령을 실행하고, 체크박스·진행 로그 갱신 후 커밋해라.
전체 골이 검증까지 끝났을 때만 마지막 줄에 정확히 GOAL_COMPLETE 를 출력해라.
아직이면 남은 태스크 3줄 요약만 하고 그 신호 문자열은 어떤 형태로도 언급하지 마라.'
while true; do
[ -f .stop-loop ] && break
OUT="$(claude --model sonnet --dangerously-skip-permissions -p "$PROMPT" 2>&1)"; ST=$?
printf '%s\n' "$OUT" >> "$LOG"
printf '%s' "$OUT" | tail -n 5 | grep -qx "GOAL_COMPLETE" && break
printf '%s' "$OUT" | grep -qiE "usage limit|limit reached|rate.?limit" && { sleep 1800; continue; }
[ $ST -ne 0 ] && { [ -z "$OUT" ] && sleep 300 || sleep 120; continue; }
sleep 120
done
포인트 몇 가지:
- 매 반복 신선한 세션.
--continue를 쓰면 반복마다 자라나는 대화 전체를 재적재해서 토큰 소모가 폭증합니다. 맥락은 원장 + git log가 대신합니다. - 구현 반복은
--model sonnet.--model을 안 주면 세션 기본값(최상위 티어)으로 돌아갑니다. 아키텍트 판단만 상위 모델에게 맡기고, 반복 실행은 저비용 모델로. - 종료는 sentinel 문자열. 다만
grep -q로 검사하면 모델이 “아직 GOAL_COMPLETE 아님”이라고 쓴 것도 매치됩니다. 반드시grep -qx(단독 라인)로 검사하고, 프롬프트에도 “미달성 시 그 문자열 언급 금지”를 넣는 이중 방어가 필요합니다. - headless는 권한 프롬프트를 못 띄우므로 무인 루프에는
--dangerously-skip-permissions가 필요합니다. 신뢰할 수 있는 환경에서만 쓰고, “검증 명령 통과 없이는 체크 금지” 게이트로 보완합니다.
3. 작업지시서 분업 - 품질은 지시서가, 속도는 루프가
이 패턴의 진짜 핵심입니다. 위임 실패의 주원인은 실행 모델의 능력 부족이 아니라 지시서에 남은 미확정 결정입니다.
그래서 역할을 둘로 나눕니다.
- 아키텍트(상위 모델): 태스크별 지시서
docs/work-orders/<ID>.md작성. 파일 경로, 시그니처, 설계 결정, 임계값, 금지사항, 수용 기준, 검증 명령까지 전부 확정.- 루프(저비용 모델): “지시서 그대로 구현. 임의 설계 판단 금지. 지시서 없으면 BLOCKED.”
지시서에 “적절히 구현”이라고 쓰는 순간 실패합니다. 수치까지 확정해야 합니다. 그리고 금지사항 목록(“이 파일은 완성도 높음, 수정 금지”)이 자율 개악을 막아줍니다.
또 하나, 실행 모델의 “완료” 보고를 믿으면 안 됩니다. 아키텍트가 grep 기반 잔여 검증(수정 대상 패턴이 정말 사라졌는지, 금지 파일이 안 바뀌었는지)을 직접 수행해야 합니다.
4. BLOCKED 프로토콜
사용자 액션이 필요하거나(웹 콘솔 설정, 로그인 토큰 등) 3회 실패하면, 태스크에 BLOCKED: 사유를 표기하고 다음 태스크로 넘어갑니다. 루프가 사람을 기다리며 멈추는 일이 없어야 밤새 돌 수 있습니다.
실제로 watchOS 프로젝트에서 이 규율이 빛났습니다. 루프가 빌드 실패의 원인이 아키텍트 담당 영역(Xcode 프로젝트 파일)에 있다는 걸 정확히 진단하고도, 원장에 “이 파일은 아키텍트 전용 - 루프는 건드리지 않는다”라고 명시돼 있어서 임의 수정하지 않고 BLOCKED 표기 후 물러났습니다. 제가 고쳐주자 루프가 알아서 재시도해 완주했고요.
교훈: 금지 영역은 태스크 단위로 원장에 이름을 박아야 지켜집니다. 추상적인 “임의 판단 금지”만으로는 부족합니다.
5. 리밋 대응 이중망
Claude Code에 리밋 자동 재개 기능은 내장돼 있지 않기 때문에, 스크립트 자체 백오프(리밋 메시지 감지 시 30분 대기)를 넣고 별도 감시를 한 겹 더 둡니다. 새벽에 리밋 걸리고 루프가 죽어있으면 슬픕니다.
6. 모니터링 상시 부착
루프를 띄웠으면 반드시 모니터를 붙입니다. “진행 어때?”라고 물어봐야 확인되는 폴링 방식은 금지 - 이벤트가 알아서 밀려오게 합니다.
- 루프 로그 감시:
tail -F+ grep으로 sentinel, BLOCKED, 리밋, 에러 시그니처만 필터. 성공 신호만 걸면 크래시가 침묵과 구별이 안 되니 실패 시그니처를 꼭 포함.- 커밋 감시:
tail -F .git/logs/HEAD- 태스크 완료 = 커밋이므로 이게 실질 진행률 신호입니다. git 폴링 방식은 커밋을 조용히 놓친 적이 있어서 reflog tail 방식을 씁니다.- 부착 직후 발화 테스트: 빈 커밋 하나로 알림이 실제로 오는지 확인. 모니터는 “조용함 = 정상”과 “조용함 = 고장”이 구별 안 되기 때문입니다.
실전 함정 모음
전부 실제로 밟은 것들입니다.
지시서 부재 공회전 사건
다음 태스크의 지시서가 안 써진 상태로 루프를 돌렸더니, 매 반복이 “BLOCKED: 지시서 없음”만 찍고 끝나는 공회전이 발생했습니다. 2분 간격으로 266회. 토큰만 신나게 태웠습니다 🥲
원인은 백오프가 실패·리밋만 구분하고 “정상 종료인데 진행이 없는” 대기성 BLOCKED를 구분하지 않았기 때문. 이후 러너에 “직전 3회 연속 동일 BLOCKED 패턴이면 30분 백오프”를 넣었고, 다음 프로젝트에서 이 장치가 실제로 발동해 아키텍트가 파일을 고치는 동안 루프가 얌전히 대기하는 걸 확인했습니다.
루프가 지시서를 스스로 쓰기 시작했다
위 공회전을 끊겠다고 어느 세션이 스스로 다음 태스크의 지시서를 써서 프로토콜을 위반한 적도 있습니다. 재밌는 건 산출물 품질이 이후 독립 검증을 통과했다는 것. 지시서 템플릿과 검증 규칙이 원장에 충분히 명문화돼 있으면 루프가 그 템플릿을 채워도 품질이 유지되더군요. 단, 이건 아키텍트의 독립 검증(자기 작성·자기 승인 금지)이 있을 때만 성립하는 예외입니다. 작성·구현·검증을 루프 혼자 다 하게 두면 안 됩니다.
지시서 스코프 누락은 루프가 못 잡는다
실행 모델은 “지시서 그대로, 임의 판단 금지”로 움직이기 때문에, 지시서에 없는 부수 작업(새 import에 따른 배포 스크립트 갱신 같은)은 절대 수행하지 않습니다. 로컬 테스트는 그린이라 검증 게이트도 통과하고, 결함은 배포 후에 터집니다. 실제로 이걸로 운영 장애를 한 번 겪었습니다.
방어는 전적으로 아키텍트 몫입니다. “코드 밖 부수 작업” 체크리스트(배포 스크립트·env·마이그레이션)를 지시서 템플릿에 상주시켜야 합니다.
로컬이 맞다고 배포본이 맞는 게 아니다
프로덕션에 이미 배포된 프로젝트에서 파일을 고치고 배포했는데 화면이 그대로였던 적이 있습니다. 정적 자산에 장기 캐시가 걸려 있어 CDN 엣지가 옛 응답을 붙들고 있었던 것(cf-cache-status: HIT). 파일명이 그대로면 내용을 바꿔도 엣지는 모릅니다.
그래서 배포가 골에 포함되면 게이트 문구를 “빌드 성공”이 아니라 “실제 도메인에서 받아서 로컬과 해시 대조”로 적습니다.
규칙은 원장에 넣어야 지켜진다
역검증(고친 코드를 되돌려 실패를 확인한 뒤 원복), 배포본 해시 대조 같은 강화 규칙을 매번 프롬프트로 지시하는 대신 원장 본문에 상시 규칙으로 박아 넣자, 이후 커밋 메시지에 “되돌리면 실패 확인” 같은 문구가 실제로 등장하며 루프가 이를 준수했습니다. 원장은 매 반복 재적재되는 유일한 영속 컨텍스트이기 때문입니다.
적용 체크리스트
새 프로젝트에 이식할 때의 순서입니다.
WORKPLAN.md작성 (골 게이트 + Phase 태스크 + 프로토콜 섹션)- 러너 스크립트 복사, sentinel·프로젝트명 치환
- 아키텍트 모델로 남은 태스크 작업지시서 일괄 작성 (
docs/work-orders/)tmux new -d -s loop './scripts/loop.sh'로 가동- 가동 직후 모니터 2개 부착 (루프 로그 + 커밋 감시)
- 주기 점검은 git log·원장 diff·BLOCKED 목록만 - transcript는 볼 필요 없습니다
마치며
이 패턴을 쓰면서 가장 크게 느낀 건, 무인 자율 실행의 품질은 모델 성능이 아니라 문서 설계에서 나온다는 점입니다. 원장이 상태를 갖고, 지시서가 결정을 갖고, 루프는 그걸 실행만 합니다. 사람(그리고 아키텍트 모델)의 일은 코딩이 아니라 결정을 미리 내려두는 것으로 바뀝니다.
자는 동안 태스크 13개가 완료되고 TestFlight 제출까지 도달해 있는 아침을 한번 경험하면 다시는 돌아갈 수 없습니다 강력 추천합니다.
한 가지 주의할 점 - --dangerously-skip-permissions로 도는 무인 루프에는 반드시 가드레일이 필요합니다. 저는 훅 기반 강제 가드레일 도구를 직접 만들어 쓰고 있는데, 관심 있으시면 oh-my-harness 소개 글도 함께 봐주세요.
