사전 준비 확인
2분헤드리스 호출은 실제 API를 사용하므로 인증 상태를 먼저 확인하고, 리뷰 대상이 될 커밋 2개짜리 실습 저장소를 만듭니다.
claude auth status || echo "로그인 필요: claude 실행 후 인증 (Chapter 1 참고)"
jq --version
mkdir -p ~/claude-lab/ch5/src && cd ~/claude-lab/ch5
cat > package.json << 'EOF'
{
"name": "cli-lab",
"version": "1.0.0",
"dependencies": { "express": "4.17.1", "lodash": "4.17.20" }
}
EOF
cat > src/pay.js << 'EOF'
export function charge(amount) {
return { ok: true, amount };
}
EOF
git init -q
git config user.name >/dev/null 2>&1 || git config user.name "lab"
git config user.email >/dev/null 2>&1 || git config user.email "lab@example.com"
git add -A && git commit -q -m "chore: cli lab scaffold"
cat > src/pay.js << 'EOF'
export function charge(amount) {
// TODO: 입력 검증 없이 그대로 청구
return { ok: true, amount, currency: "KRW" };
}
EOF
git commit -qam "feat: add currency to charge"
git log --oneline
헤드리스 5패턴, 자동화의 어휘
10분앞으로 모든 자동화에서 반복될 5가지 기본 패턴을 손에 익힙니다. 각 명령을 실행하고 출력 구조를 관찰하세요.
claude -p "리스트와 튜플의 차이를 정확히 3줄로"
claude -p "정렬 알고리즘 3가지 이름만" --output-format json | jq -r '.result'
claude -p "OK 한 단어만" --output-format json | \
jq '{in: .usage.input_tokens, out: .usage.output_tokens, usd: .total_cost_usd, ms: .duration_ms}'
{ "in": 4213, "out": 5, "usd": 0.013, "ms": 2841 }
cat package.json | claude -p "이 의존성 목록에서 눈에 띄는 위험 하나만 한 줄로"
S=$(claude -p "정렬 알고리즘 3가지 이름만" --output-format json | jq -r '.session_id')
echo "session: $S"
claude -p --resume "$S" "그 중 첫 번째 것만 파이썬 코드로" --output-format json | jq -r '.result'
claude --continue "$SESSION_ID"는 현행과 다릅니다.
-c / --continue는 ID 없이 현재 디렉토리의 최근 대화를 잇고,
특정 세션을 ID나 이름으로 지정할 때는 -r / --resume을 씁니다.
분기해서 실험하고 싶으면 --resume "$S" --fork-session으로 새 세션 ID를 만듭니다.
claude --bare -p "..."로
시작 오버헤드를 줄일 수 있습니다. 반복 호출 파이프라인에서 체감이 큽니다.
자동화 스크립트, 로컬 리뷰 봇
10분커밋 범위를 인자로 받아 diff를 리뷰하고, 결과를 파일로 남기며, 심각도에 따라 exit code로 신호하는 스크립트를 만듭니다. 이 exit code가 Task 4에서 CI 게이트가 됩니다.
cd ~/claude-lab/ch5
cat > my-review.sh << 'EOF'
#!/bin/bash
# my-review.sh <커밋범위> - 로컬 코드 리뷰 봇
set -euo pipefail
RANGE="${1:?Usage: ./my-review.sh <commit-range> 예: HEAD~1..HEAD}"
echo "1) diff 수집: $RANGE"
DIFF=$(git diff "$RANGE")
[ -z "$DIFF" ] && { echo "diff 없음"; exit 0; }
echo "2) Claude 리뷰 실행..."
RESULT=$(claude -p "$(cat <<PROMPT
다음 diff를 리뷰하세요.
관점: 1) 버그 위험 2) 보안 3) 테스트 필요성
각 지적은 "- [심각도] 파일: 내용" 형식, 심각도는 CRITICAL/WARN/INFO 중 하나.
심각한 문제가 없으면 "- [INFO] 특이사항 없음" 한 줄만.
$DIFF
PROMPT
)" --output-format json --max-turns 5 \
--allowed-tools "Read" "Grep" "Glob")
echo "$RESULT" | jq -r '.result' > review.md
COST=$(echo "$RESULT" | jq -r '.total_cost_usd')
echo "3) 저장: review.md (비용: \$$COST)"
if grep -q "CRITICAL" review.md; then
echo "4) CRITICAL 발견 → exit 1 (게이트 차단 신호)"
exit 1
fi
echo "4) 통과"
EOF
chmod +x my-review.sh
bash -n my-review.sh && echo "문법 OK"
./my-review.sh HEAD~1..HEAD; echo "exit code: $?"
cat review.md
1) diff 수집: HEAD~1..HEAD
2) Claude 리뷰 실행...
3) 저장: review.md (비용: $0.021)
4) 통과
exit code: 0
- [WARN] src/pay.js: amount 입력 검증 부재, 음수/비숫자 방어 필요
- [INFO] 테스트 파일 부재 ...
set -euo pipefail은 중간 실패를 즉시 드러내고,
--max-turns 5는 폭주를 막고,
--allowed-tools "Read" "Grep" "Glob"은 읽기 전용으로 제한합니다(공백 구분 다중 인자 형식).
예산 상한이 필요하면 --max-budget-usd 0.50을 더하세요.
gh pr diff "$PR_NUM"으로 바꾸고,
마지막에 gh pr comment "$PR_NUM" --body-file review.md를 붙이면 그대로 PR 리뷰 봇이 됩니다.
사람 확인 후 게시하도록 read 프롬프트를 두는 것이 강의에서 다룬 안전 패턴입니다.
파싱 파이프라인, JSON에서 대시보드까지
10분여러 호출의 JSON을 모아 CSV로 변환하고 통계를 집계한 뒤 HTML 대시보드로 만듭니다. 조직의 사용량/비용 리포트 자동화가 이 구조의 확장판입니다.
비용 절약을 위해 --model haiku로 수집합니다.
cd ~/claude-lab/ch5 && mkdir -p data
for topic in Python Go Rust; do
echo "수집: $topic"
claude -p "$topic 언어의 장점을 정확히 2가지, 각 한 줄로" \
--model haiku --output-format json > "data/$topic.json"
done
ls -la data/
{
echo "topic,duration_ms,input_tokens,output_tokens,cost_usd"
jq -r '[
(input_filename | sub("data/"; "") | sub("\\.json$"; "")),
.duration_ms, .usage.input_tokens, .usage.output_tokens, .total_cost_usd
] | @csv' data/*.json
} > stats.csv
cat stats.csv
topic,duration_ms,input_tokens,output_tokens,cost_usd
"Go",2113,4188,42,0.0031
"Python",2410,4190,45,0.0032
"Rust",2287,4189,44,0.0031
jq -rs '.[] | [(input_filename | ...)]' 패턴은
슬러프가 모든 파일을 먼저 읽어버려 모든 행에 마지막 파일명이 찍힙니다.
파일당 JSON이 하나라면 위처럼 -s 없이 파일별 처리가 정답입니다.
반대로 아래 통계 집계는 전체를 하나의 배열로 봐야 하므로 -s가 맞습니다.
jq -rs '{
total_calls: length,
avg_duration_ms: ([.[].duration_ms] | add / length | floor),
total_input: ([.[].usage.input_tokens] | add),
total_output: ([.[].usage.output_tokens] | add),
total_cost_usd: ([.[].total_cost_usd] | add)
}' data/*.json
cat << HTML > dashboard.html
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Claude Usage</title>
<style>
body { font-family: sans-serif; max-width: 760px; margin: 40px auto; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 8px 10px; border-bottom: 1px solid #ddd; text-align: left; }
th { background: #f0f0f0; }
</style></head><body>
<h1>Claude 호출 통계</h1>
<p>생성: $(date)</p>
$(awk -F, 'BEGIN{print "<table>"}
NR==1{printf "<tr>"; for(i=1;i<=NF;i++) printf "<th>%s</th>", $i; print "</tr>"; next}
{gsub(/"/,""); printf "<tr>"; for(i=1;i<=NF;i++) printf "<td>%s</td>", $i; print "</tr>"}
END{print "</table>"}' stats.csv)
</body></html>
HTML
echo "생성 완료: dashboard.html (브라우저로 열어 확인)"
--max-budget-usd로 상한을 거는 것이 안전합니다.
스트리밍 이벤트 단위 처리가 필요하면 --output-format stream-json을 사용합니다.
CI 통합, GitHub Actions 워크플로
8분Task 2의 리뷰 봇 개념을 CI로 올립니다. 워크플로 작성과 문법 검증은 전원 진행하고, 실제 push 검증은 GitHub 저장소 보유자용 선택 트랙입니다.
cd ~/claude-lab/ch5
mkdir -p .github/workflows
cat > .github/workflows/claude-check.yml << 'EOF'
name: Claude Check
on:
pull_request:
types: [opened, synchronize]
workflow_dispatch:
permissions:
contents: read
jobs:
claude-check:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Review diff
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
git diff origin/${{ github.base_ref }}...HEAD | \
claude -p "이 diff에서 CRITICAL 수준 문제만 지적, 없으면 PASS 한 단어" \
--output-format json --max-turns 3 --max-budget-usd 0.50 | \
jq -r '.result' | tee check.md
grep -q "CRITICAL" check.md && exit 1 || echo "게이트 통과"
EOF
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/claude-check.yml')); print('YAML 문법 OK')"
| 경로 | 방법 | 적합 |
|---|---|---|
| API Key | 저장소 Settings → Secrets → ANTHROPIC_API_KEY 등록 | Console 계정 팀 |
| 구독 토큰 | 로컬에서 claude setup-token으로 장수명 토큰 발급 후 Secret 등록 | Pro/Max 구독 CI |
| Bedrock | OIDC로 AWS 역할 Assume + CLAUDE_CODE_USE_BEDROCK=1 | 키 없는 조직 표준 (Ch3) |
git checkout -b test-claude-ci
git add .github && git commit -m "ci: add claude check"
git push -u origin test-claude-ci
gh pr create --title "Test Claude CI" --body "Testing" --fill 2>/dev/null || true
gh run watch
timeout-minutes(작업 시간 상한), --max-turns(에이전트 턴 상한),
--max-budget-usd(지출 상한)를 항상 함께 거세요.
permissions는 최소로 시작하고(PR 코멘트가 필요할 때만 pull-requests: write 추가),
시크릿은 organization secret으로 중앙 관리하는 것이 회전에 유리합니다.
마무리, 학습 목표 체크리스트
Chapter 5의 학습 목표를 스스로 점검하세요. 미달성 항목은 진행자에게 질문하거나 강의 자료의 해당 Part를 다시 확인합니다.
| 목표 | 확인 질문 | 관련 |
|---|---|---|
| Headless | -p / JSON / 파이프 / --resume 5패턴을 손으로 실행했는가 | Task 1 |
| Scripting | diff 주입, 도구 제한, exit code 게이트 스크립트를 만들었는가 | Task 2 |
| Parsing | 다중 호출 JSON을 CSV와 대시보드로 집계했는가 | Task 3 |
| CI/CD | 3중 잠금이 걸린 워크플로를 작성하고 검증했는가 | Task 4 |
| 패턴 | 구조 | 오늘 만든 재료 |
|---|---|---|
| 1 PR 리뷰 봇 | diff 주입 → 리뷰 → 코멘트/게이트 | Task 2 + Task 4 그대로 |
| 2 이슈 트리아지 | 이슈 본문 파이프 → 분류 JSON → 라벨링 | Task 1 패턴 4 + jq |
| 3 코드 마이그레이션 | 파일 루프 → 변환 → 실패 수집 | Task 3 루프 구조 |
| 4 보안 감사 | 주기 실행 → CRITICAL 게이트 → 알림 | Task 2 exit code + cron |
| 5 일일 보고서 | 다중 수집 → 집계 → HTML 발행 | Task 3 파이프라인 그대로 |