본문으로 건너뛰기
AIPida
채택됨고급업무 자동화

Claude Code를 cron/CI에서 비대화형(headless)으로 돌려서 코드 자동화하는 법

Claude Code를 평소엔 터미널에서 대화형으로 쓰는데, 이걸 자동화 파이프라인에 넣고 싶습니다.

구체적으로는 매주 의존성 업데이트(renovate/dependabot)가 올라오면 자동으로 변경분을 점검하고, 간단한 마이그레이션이나 changelog 정리를 시켜서 PR 초안까지 만들게 하려고요. 사람이 앉아서 프롬프트를 치는 게 아니라 스크립트가 호출하는 형태여야 합니다.

비대화형(headless) 호출 방법, 출력을 프로그램에서 받아서 다음 단계로 넘기는 방법, 그리고 무인 실행 시 권한/안전 측면에서 주의할 점이 궁금합니다. 실제로 이렇게 운영하시는 분 계신가요?

답변 3

  • 채택된 답변에디터 검증

    CI랑 cron에서 실제로 돌리는 중입니다. 질문 세 개 순서대로요.

    비대화형 호출claude -p "프롬프트" print 모드. 대화형 UI 없이 한 번 실행하고 결과를 stdout으로 뱉어서 스크립트에서 그냥 캡처하면 됩니다. 프로그램에서 파싱할 거면 --output-format json(또는 스트리밍이면 stream-json)으로 받으세요. 결과 텍스트 + 비용/turn 수 같은 메타데이터가 구조적으로 떨어져서 다음 단계로 넘기기 깔끔합니다.

    근데 솔직히 호출 방법보다 권한이 본체예요. 대화형이면 위험한 작업마다 사람이 y/n 누르지만 무인엔 그 사람이 없잖아요. 두 개는 꼭:

    • 허용 도구를 명시적으로 화이트리스트 (--allowedTools로 필요한 것만). --dangerously-skip-permissions(흔히 yolo 모드라고 부르는 그거) 같은 건 진짜 일회용 격리 컨테이너에서, 그것도 최소 범위로만. 로컬 개발머신에서 무인+skip-permissions 돌리는 건 사고 직행입니다.
    • 결과는 PR로만 나가게. main 직접 푸시 금지. 사람이 PR 보고 머지하는 게 마지막 안전판이에요.

    그리고 프롬프트로 변경 범위를 좁히세요 — "의존성 업데이트 관련 파일만 수정, 그 외 리팩터 금지" 식으로. 범위 안 박으면 changelog 정리하랬는데 옆에 보이는 코드까지 "개선"하겠다고 손대는 경우가 있어요.

    저희 구조는 결국 에이전트가 PR 초안 → CI(테스트/린트) 자동 검증 → 사람이 최종 머지인데, 무인이라도 머지 게이트에 사람 하나 두는 게 제일 사고를 막았습니다. 완전 무인 머지는 한두 번 데어보면 자연스럽게 안 하게 돼요.

  • PR로만 내보내고 main 직접 못 푸시하게 막으라는 거, 진짜 동의합니다. 무인 에이전트가 "성공했습니다"라고 당당하게 보고해도 실제론 반쪽만 된 경우가 꽤 있어서, 사람 머지 게이트가 사실상 마지막 검증이에요.

    출력 파싱 관련 한 가지. --output-format json으로 받아도 그 envelope 안의 result 텍스트는 또 자유 형식이라, 거기서 PR 제목/본문 뽑아 쓸 거면 그 본문 포맷도 프롬프트에 못박아 두세요. 안 그러면 모델이 ```markdown 펜스로 감싸서 주거나 앞에 "네, 정리했습니다" 같은 사족을 붙여서 정규식이 깨집니다.

    그리고 cron이면 동시 실행 락 잊지 마세요. 이전 회차가 안 끝났는데 다음 회차가 또 시작되면 같은 브랜치 두고 git이 충돌나거나, 운 나쁘면 서로 force push 합니다. flock이든 깃헙 concurrency group이든 하나 거세요.

  • 비용 쪽 하나만. 무인이면 사람이 안 보는 사이 에이전트가 한 이슈 붙들고 30분씩 빙빙 돌면서 토큰을 태우는 일이 생겨요. turn 수나 wall-clock 타임아웃 상한을 오케스트레이터 레벨에서 걸어두세요. 모델한테 "적당히 해"라고 부탁하는 거 말고 외부에서 끊는 거요.

    그리고 실행 로그(무엇을 왜 바꿨는지)는 꼭 남기세요. 나중에 그 PR 리뷰할 때 에이전트 판단 근거를 못 따라가면 그냥 블랙박스 diff가 돼서 리뷰가 더 오래 걸립니다. 무인일수록 observability가 생명이에요.