Claude Code CI/CD 통합 가이드: GitHub Actions 자동화
GitHub Actions에서 Claude Code를 실행하면 PR이 열릴 때마다 자동으로 코드 리뷰 댓글이 달리고, 린트·테스트 실패 시 Claude가 수정 코드를 직접 커밋한다. 설정은 YAML 파일 하나와 ANTHROPIC_API_KEY 시크릿 등록으로 끝난다. Claude Haiku 기준 PR 리뷰 한 건에 약 15–25원(₩), 자동 수정 시도까지 포함해도 월 5달러 미만으로 운영할 수 있다.
CI/CD에서 Claude Code를 사용하는 이유
전통적인 정적 분석 도구(ESLint, flake8, SonarQube)는 규칙 기반이다. 룰셋 밖의 문제는 잡지 못하고, 컨텍스트 없는 경고만 쏟아낸다. Claude는 다르다.
- 비즈니스 로직 이해: "이 할인율 계산이 소수점 버림으로 인해 음수가 될 수 있습니다" 같은 도메인 맥락 리뷰
- 자연어 피드백: 리뷰어가 직접 쓴 것 같은 한국어 코멘트, 코드 스니펫 포함
- 자동 수정 루프: 테스트 실패 → Claude 원인 분석 → 수정 코드 생성 → 재실행 패턴
- 일관성: 리뷰어 컨디션·숙련도와 무관하게 동일 기준 적용
팀 규모와 무관하게 Solo 개발자부터 수십 명 팀까지 즉시 도입할 수 있다. Claude Code 전체 기능은 Claude Code 완전 가이드를 참고하라.
1단계: ANTHROPIC_API_KEY 보안 설정
GitHub Actions에 API 키를 등록한다. 코드에 직접 키를 입력하는 것은 절대 금지다.
Repository Secret 등록
- 리포지토리 → Settings → Secrets and variables → Actions
- New repository secret 클릭
- Name:
ANTHROPIC_API_KEY, Value: Anthropic 콘솔에서 발급한 키 입력 - Add secret 저장
조직 단위로 여러 리포지토리에 공통 적용하려면 Organization secrets를 사용한다.
워크플로우에서 참조하는 방법
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
secrets.ANTHROPIC_API_KEY는 로그에 마스킹되어 ***로 표시된다. 절대 echo $ANTHROPIC_API_KEY로 값을 출력하지 말 것.
2단계: GitHub Actions 기본 설정
.github/workflows/ 디렉토리에 YAML 파일을 생성한다. 아래는 모든 PR 워크플로우의 공통 뼈대다.
# .github/workflows/claude-pr-review.yml
name: Claude PR 리뷰
on:
pull_request:
types: [opened, synchronize, reopened]
# 문서·설정 파일 변경은 제외 (비용 절감)
paths-ignore:
- "**.md"
- "**.txt"
- ".github/**"
- "docs/**"
# 최소 권한 원칙
permissions:
contents: read
pull-requests: write
# 동일 PR에 중복 실행 방지
concurrency:
group: claude-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
claude-review:
runs-on: ubuntu-latest
# Dependabot PR, Draft PR 건너뛰기
if: |
github.actor != 'dependabot[bot]' &&
github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Python SDK 설치
run: pip install anthropic --quiet
이 뼈대에서 핵심은 세 가지다.
paths-ignore: 마크다운·문서만 변경된 PR에서 Claude를 실행하지 않아 불필요한 API 비용을 줄인다.concurrency: 같은 PR에 연속 푸시가 있을 때 이전 실행을 취소하고 최신만 실행한다.if조건: Dependabot 자동 PR과 Draft PR은 리뷰 불필요하므로 건너뛴다.
3단계: PR 자동 리뷰 워크플로우 (전체 YAML)
# .github/workflows/claude-pr-review.yml (전체)
name: Claude PR 리뷰
on:
pull_request:
types: [opened, synchronize, reopened]
paths-ignore:
- "**.md"
- "**.txt"
- ".github/**"
- "docs/**"
permissions:
contents: read
pull-requests: write
concurrency:
group: claude-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
claude-review:
runs-on: ubuntu-latest
if: |
github.actor != 'dependabot[bot]' &&
github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Python SDK 설치
run: pip install anthropic --quiet
- name: PR diff 수집
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
# diff를 파일로 저장 (최대 50KB)
gh pr diff "${PR_NUMBER}" | head -c 51200 > pr_diff.txt
# PR 메타 정보 수집
gh pr view "${PR_NUMBER}" --json title,body,labels > pr_meta.json
- name: Claude 코드 리뷰 실행
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
# API 제한 시 워크플로우 전체를 실패시키지 않음
continue-on-error: true
run: |
python3 - <<'PYEOF'
import anthropic, os, json
pr_title = os.environ.get("PR_TITLE", "")
with open("pr_diff.txt") as f:
diff = f.read()
with open("pr_meta.json") as f:
meta = json.load(f)
pr_body = meta.get("body") or "(설명 없음)"
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=2500,
system=(
"당신은 시니어 소프트웨어 엔지니어입니다. "
"코드 리뷰를 한국어로 작성하세요. "
"구체적이고 실행 가능한 피드백만 제공하세요."
),
messages=[{
"role": "user",
"content": (
f"## PR 제목\n{pr_title}\n\n"
f"## PR 설명\n{pr_body}\n\n"
f"## 변경 코드 (git diff)\n```diff\n{diff}\n```\n\n"
"다음 순서로 리뷰해 주세요:\n"
"1. **요약** (2-3줄): 이 PR이 무엇을 바꾸는지\n"
"2. **🔴 버그 / 보안 취약점**: 즉시 수정 필요한 항목 (없으면 생략)\n"
"3. **🟡 경고**: 잠재적 성능 문제, 엣지 케이스 (없으면 생략)\n"
"4. **🔵 제안**: 코드 품질 개선 아이디어 (최대 3개)\n"
"5. **✅ 평가**: LGTM / Changes Requested / Needs Discussion 중 하나\n\n"
"각 지적 사항에는 파일명과 줄 번호를 포함하고, "
"수정 방법을 코드 예시로 보여주세요."
)
}]
)
review_text = response.content[0].text
input_tokens = response.usage.input_tokens
output_tokens = response.usage.output_tokens
# Haiku 가격: 입력 $0.80/MTok, 출력 $4.00/MTok
cost_usd = (input_tokens * 0.0000008) + (output_tokens * 0.000004)
cost_krw = cost_usd * 1380
body = (
"## 🤖 Claude 코드 리뷰\n\n"
f"{review_text}\n\n"
"---\n"
f"*Claude Haiku · {input_tokens + output_tokens:,} 토큰 · "
f"₩{cost_krw:.0f} (${cost_usd:.4f})*"
)
with open("review_comment.md", "w") as f:
f.write(body)
PYEOF
- name: PR에 리뷰 댓글 게시
if: success() || steps.*.outcome == 'success'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
if [ -f review_comment.md ]; then
gh pr comment "${PR_NUMBER}" --body-file review_comment.md
fi
4단계: 테스트 실패 시 자동 수정 패턴
테스트가 실패하면 Claude가 원인을 분석하고 수정 코드를 생성한 뒤 커밋까지 올린다.
# .github/workflows/claude-auto-fix.yml
name: Claude 자동 수정
on:
push:
branches-ignore:
- main
- master
permissions:
contents: write
pull-requests: write
jobs:
test-and-fix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# 봇이 커밋·푸시할 수 있도록 토큰 교체
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: 의존성 설치 및 테스트 실행
id: run-tests
# 실패해도 다음 스텝 진행 (Claude가 수정 시도)
continue-on-error: true
run: |
# Python 프로젝트 예시 — 프로젝트에 맞게 교체
pip install -r requirements.txt --quiet 2>/dev/null || true
python -m pytest tests/ -v 2>&1 | tee test_output.txt
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
- name: Python SDK 설치
if: steps.run-tests.outcome == 'failure'
run: pip install anthropic --quiet
- name: Claude 자동 수정 시도
if: steps.run-tests.outcome == 'failure'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
python3 - <<'PYEOF'
import anthropic, os, subprocess, json
# 실패한 테스트 출력 읽기
with open("test_output.txt") as f:
test_output = f.read()[-8000:] # 마지막 8KB
# 변경된 소스 파일 파악
changed = subprocess.check_output(
["git", "diff", "origin/main...HEAD", "--name-only"]
).decode().strip().splitlines()
src_files = [
f for f in changed
if f.endswith((".py", ".ts", ".js"))
and not any(x in f for x in ("test", "spec", "__pycache__"))
]
# 관련 소스 파일 내용 수집
file_contents = {}
for path in src_files[:4]:
if os.path.exists(path):
with open(path) as fp:
file_contents[path] = fp.read()[:5000]
if not file_contents:
print("수정할 소스 파일을 찾을 수 없습니다.")
exit(0)
files_block = "\n\n".join(
f"### {path}\n```\n{code}\n```"
for path, code in file_contents.items()
)
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5", # 수정은 Sonnet 사용 (정확도 중요)
max_tokens=4000,
system=(
"당신은 시니어 엔지니어입니다. "
"테스트 실패를 분석하고 최소한의 코드 변경으로 수정하세요. "
"각 파일 수정 사항을 JSON으로 반환하세요: "
'{"fixes": [{"path": "파일경로", "content": "전체 파일 내용"}]}'
),
messages=[{
"role": "user",
"content": (
f"## 테스트 실패 출력\n```\n{test_output}\n```\n\n"
f"## 관련 소스 파일\n{files_block}\n\n"
"위 테스트 실패를 수정하는 JSON을 반환해 주세요."
)
}]
)
text = response.content[0].text
# JSON 블록 추출
import re
match = re.search(r'\{.*"fixes".*\}', text, re.DOTALL)
if not match:
print("Claude가 수정 JSON을 반환하지 않았습니다.")
print(text[:500])
exit(0)
result = json.loads(match.group())
fixes = result.get("fixes", [])
for fix in fixes:
path = fix.get("path")
content = fix.get("content")
if path and content and os.path.exists(path):
with open(path, "w") as f:
f.write(content)
print(f"수정 완료: {path}")
if fixes:
with open("fix_summary.txt", "w") as f:
paths = [f["path"] for f in fixes]
f.write(f"Claude 자동 수정: {', '.join(paths)}")
PYEOF
- name: 수정 후 테스트 재실행
if: steps.run-tests.outcome == 'failure'
id: rerun-tests
continue-on-error: true
run: |
python -m pytest tests/ -v 2>&1 | tee test_output_fixed.txt
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
- name: 수정 코드 커밋 및 푸시
if: |
steps.run-tests.outcome == 'failure' &&
steps.rerun-tests.outcome == 'success'
run: |
git config user.name "claude-autofix[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -u
SUMMARY=$(cat fix_summary.txt 2>/dev/null || echo "테스트 실패 자동 수정")
git commit -m "fix(auto): ${SUMMARY} [claude-autofix]"
git push
자동 수정 패턴의 핵심 원칙
모델 선택: 리뷰는 Haiku(속도·비용), 수정은 Sonnet(정확도). 수정 코드의 품질이 중요하므로 더 강력한 모델을 사용한다.
2단계 루프: 수정 후 테스트를 재실행한다. 재실행에서도 실패하면 커밋하지 않는다. 잘못된 수정을 자동 커밋하는 것보다 실패를 그대로 남기는 것이 낫다.
최소 변경 원칙: 프롬프트에 "최소한의 코드 변경"을 명시한다. Claude가 리팩토링까지 시도하면 리뷰 범위가 커지고 실수 확률도 높아진다.
5단계: 비용 및 보안 고려사항
비용 관리
| 시나리오 | 모델 | 건당 비용 | 월 50 PR 기준 |
|---|---|---|---|
| PR 리뷰 (200줄 diff) | Haiku | ₩18 | ₩900 |
| 자동 수정 시도 | Sonnet | ₩120 | ₩6,000 (실패 PR 20%만) |
| 전체 합계 | — | — | ≈₩7,000/월 |
비용 절감 전략 4가지:
paths-ignore:.md,docs/**,*.json등 코드가 아닌 파일 제외- Draft PR 제외:
github.event.pull_request.draft == false조건 추가 - diff 크기 제한:
head -c 51200으로 50KB 이상 diff는 잘라낸다. 대형 PR은 요약 리뷰만 한다. - Anthropic 예산 한도: 콘솔 → Billing → Spending limits 에서 월 예산 설정
보안 체크리스트
# ✅ 올바른 방법
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# ❌ 절대 금지 — 키가 로그에 노출됨
env:
ANTHROPIC_API_KEY: "sk-ant-api03-..."
권한 최소화: 리뷰만 하는 워크플로우는 pull-requests: write면 충분하다. contents: write는 자동 수정 커밋이 필요한 경우에만 추가한다.
포크 PR 주의: 외부 기여자의 포크에서 온 PR은 기본적으로 secrets에 접근할 수 없다. pull_request_target 이벤트를 사용하면 secrets 접근이 가능하지만, 악의적인 코드 실행 위험이 있으므로 신중히 설정한다.
데이터 전송 범위: CI가 Claude API에 보내는 코드는 Anthropic 서버를 통과한다. 사내 기밀 코드가 포함된 비공개 리포지토리는 Anthropic의 데이터 사용 정책을 사전 확인하라. 엔터프라이즈 계정은 Zero Data Retention 옵션을 활용할 수 있다.
Claude Code CLI를 직접 Actions에서 실행하기
Python SDK 대신 Claude Code CLI(@anthropic-ai/claude-code)를 직접 설치해 실행할 수도 있다.
- name: Claude Code CLI 설치 및 실행
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
npm install -g @anthropic-ai/claude-code --quiet
# --print 플래그: 대화 없이 단일 응답 출력
REVIEW=$(claude --print "다음 diff를 한국어로 리뷰해 주세요. 버그와 보안 문제 위주로." \
< pr_diff.txt)
echo "$REVIEW" > review_cli.md
--print 플래그는 인터랙티브 모드 없이 단일 응답을 출력한다. CI 환경에 적합하다. Claude Code Hooks와의 고급 통합 패턴은 Claude Code Hooks 가이드를 참고하라.
GitHub Actions 즉시 사용 프롬프트 50개
Power Prompts ($29)에는 이 가이드에서 다룬 PR 리뷰, 자동 수정, 배포 위험도 분석을 위한 프롬프트 템플릿이 포함되어 있습니다. 복사-붙여넣기로 바로 사용 가능.
Frequently Asked Questions
GitHub Actions에서 ANTHROPIC_API_KEY를 설정하는 방법은?
리포지토리 Settings → Secrets and variables → Actions → New repository secret에서 이름 ANTHROPIC_API_KEY, 값에 Anthropic 콘솔 API 키를 입력한다. 워크플로우 YAML에서는 ${{ secrets.ANTHROPIC_API_KEY }}로 참조한다. 조직 내 여러 리포지토리에 공통 적용하려면 Organization 레벨 secret을 사용하면 한 번만 설정해도 된다.
Claude가 PR에 중복 댓글을 계속 달지 않게 하려면?
concurrency 블록을 사용한다. group: claude-review-${{ github.event.pull_request.number }}로 같은 PR에 동시 실행되는 워크플로우를 하나로 제한하고 cancel-in-progress: true를 설정하면, 연속 푸시 시 이전 실행이 취소되고 최신 것만 실행된다. 기존 댓글을 삭제하고 새 댓글을 달고 싶다면 gh pr comment list --json id로 이전 댓글 ID를 가져와 삭제 후 재작성한다.
자동 수정이 테스트를 통과해도 코드 품질이 걱정된다면?
자동 수정 워크플로우에서 수정 코드를 main에 직접 머지하지 않고 별도 브랜치에 커밋하도록 설정한다. 그런 다음 Claude가 수정 내역을 설명하는 PR을 자동으로 열게 하면, 개발자가 최종 리뷰 후 머지할 수 있다. gh pr create --title "fix(auto): 테스트 수정" --body-file fix_summary.txt를 커밋 후 실행하면 된다.
포크 PR에서 Claude 리뷰가 작동하지 않는 이유는?
기본 pull_request 이벤트는 보안상 포크 PR에서 secrets 접근을 차단한다. 포크 PR에도 리뷰를 적용하려면 pull_request_target 이벤트로 변경해야 한다. 단, 이 이벤트는 base 브랜치 컨텍스트에서 실행되므로 포크의 코드를 체크아웃하기 전에 반드시 보안 검토가 필요하다. 신뢰할 수 없는 코드를 실행하는 스텝과 secrets를 사용하는 스텝을 분리해야 한다.
비용을 더 줄이고 싶다면 어떤 전략이 효과적인가?
가장 큰 효과는 diff 크기 제한이다. head -c 30000으로 30KB로 줄이면 토큰이 절반 이하로 감소한다. 두 번째는 paths-ignore로 리뷰 불필요한 파일 유형을 제외한다. 세 번째는 라벨 필터를 추가해 skip-ai-review 라벨이 붙은 PR은 건너뛰는 로직을 넣는다. 이 세 가지만 적용해도 실제 API 호출 횟수가 40–60% 줄어든다. 상세 비용 계산은 Anthropic 콘솔의 Usage 탭에서 일별로 확인할 수 있다.