ZIP
Hands-on Lab / Chapter 2

Subagents, 전문 에이전트를 만들고 지휘하기

Claude Code Deep Dive Workshop, Chapter 2 - Agents (Subagents) 실습

이 랩에서는 code-reviewer, test-writer, docs-writer 3종의 서브에이전트를 직접 정의하고, 도구 권한 격리를 체험한 뒤 자동 / 명시 / 병렬 디스패치 3가지 패턴과 Headless CI 통합까지 실습합니다. Chapter 1에서 만든 ~/claude-lab/ch1 프로젝트를 이어서 사용합니다.

소요 시간 40분 내외 Task 준비 + 5개 기준 버전 Claude Code 2.1.267 Update 2026.09
TASK FLOW

이 챕터의 진행 흐름

개요

전반의 세 Task로 전문 에이전트를 만들고, 후반의 세 Task로 그들을 지휘하는 패턴까지 나아갑니다.
진행 중 위치가 헷갈리면 이 흐름도로 돌아오세요.

상세 흐름도 펼쳐 보기
만들기, 전문 에이전트 T0 사전 준비 환경 확인 T1 code-reviewer 첫 Subagent T2 권한 격리 도구 최소 권한 지휘하기, 팀으로 확장 T3 에이전트 3종 test, docs-writer T4 디스패치 3패턴 위임, 병렬, 체이닝 T5 Headless + CI 파이프라인 맛보기 에이전트를 만들고, 지휘의 패턴으로
TASK 00

사전 준비 확인

2분

Chapter 1 실습 프로젝트가 커밋 2개 이상(초기 + 버그 수정) 상태인지 확인합니다.
Task 1의 첫 리뷰 대상이 이 수정 커밋의 diff입니다.

터미널 위치 이 랩은 Chapter 1에서 만든 ~/claude-lab/ch1에서 그대로 이어집니다. 아래에 나오는 .claude/agents/ 같은 상대 경로도 전부 이 폴더 기준입니다. 시작 전에 cd ~/claude-lab/ch1로 위치를 맞추세요.
1프로젝트 상태 확인
Terminal
cd ~/claude-lab/ch1
npm test          # 3개 모두 PASS 여야 합니다
git log --oneline # 커밋 2개 이상이어야 합니다
출력 예시
$ cd ~/claude-lab/ch1
npm test          # 3개 모두 PASS 여야 합니다
git log --oneline # 커밋 2개 이상이어야 합니다

> user-service-lab@1.0.0 test
> node test.js

Test 1 - pro 플랜 사용자: PASS
Test 2 - free 플랜 사용자: PASS
Test 3 - profile 없는 사용자: PASS
Test 4 - 사용자 삭제 성공 시 삭제 시각(문자열) 반환: PASS
Test 5 - 삭제된 사용자는 plan 조회 시 unknown 반환: PASS
Test 6 - 존재하지 않는 사용자 삭제 시 예외 대신 null 반환: PASS
Test 7 - 이미 삭제된 사용자 재삭제 시 null 반환: PASS
dfa9bfd (HEAD -> fix/user-plan-default) fix: profile 없는 사용자의 요금제 조회 시 기본값 반환
6819730 (master) chore: initial lab project
Chapter 1을 건너뛰었거나 프로젝트가 없는 경우에만 아래 블록을 통째로 실행하면 Chapter 1 완료 상태(버그 수정 커밋 포함)로 프로젝트가 재생성됩니다. 이미 프로젝트가 있다면 실행하지 마세요.
Terminal, 복구용 전체 복사
mkdir -p ~/claude-lab/ch1/src && cd ~/claude-lab/ch1

cat > package.json << 'EOF'
{
  "name": "user-service-lab",
  "version": "1.0.0",
  "description": "Claude Code Workshop sample project",
  "type": "module",
  "scripts": { "test": "node test.js" }
}
EOF

cat > src/users.js << 'EOF'
// 사용자 데이터 저장소
// 주의: 일부 사용자는 profile 필드가 없음 (마이그레이션 미수행 상황)
export const users = [
  { id: 1, name: "Kim", profile: { email: "kim@example.com", plan: "pro" } },
  { id: 2, name: "Lee", profile: { email: "lee@example.com", plan: "free" } },
  { id: 3, name: "Park" }
];
EOF

cat > src/userService.js << 'EOF'
import { users } from "./users.js";

export function getUser(id) {
  return users.find((u) => u.id === id);
}

export function getUserPlan(id) {
  const user = getUser(id);
  return user.profile.plan;
}
EOF

cat > test.js << 'EOF'
import { getUserPlan } from "./src/userService.js";

function check(name, fn, expected) {
  const actual = fn();
  console.log(`${name}: ${actual === expected ? "PASS" : `FAIL (got ${actual})`}`);
}

check("Test 1 - pro 플랜 사용자", () => getUserPlan(1), "pro");
check("Test 2 - free 플랜 사용자", () => getUserPlan(2), "free");
check("Test 3 - profile 없는 사용자", () => getUserPlan(3), "unknown");
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: initial lab project (with bug)"

cat > src/userService.js << 'EOF'
import { users } from "./users.js";

export function getUser(id) {
  return users.find((u) => u.id === id);
}

export function getUserPlan(id) {
  const user = getUser(id);
  return user?.profile?.plan ?? "unknown";
}
EOF

git add -A && git commit -q -m "fix: handle users without profile"
npm test && git log --oneline
2jq 설치 확인

Task 5의 헤드리스 통합에서 JSON 출력 파싱에 jq를 사용합니다. 없으면 한 줄로 설치하세요.

Terminal, 전체 복사
jq --version || sudo dnf install -y jq
출력 예시
jq-1.8.1
CHECKPOINT
TASK 01

첫 Subagent, code-reviewer 만들기

8분

.claude/agents/에 code-reviewer를 정의하고 등록을 확인한 뒤, Chapter 1의 버그 수정 커밋을 첫 리뷰 대상으로 호출합니다.

Subagent가 왜 필요한가, Context Isolation 서브에이전트는 자신만의 200K 컨텍스트, 시스템 프롬프트, 도구 권한, 모델을 갖는 독립 에이전트입니다. 파일 수십 개를 읽는 무거운 분석을 위임하면 서브에이전트 컨텍스트에서 소비되고, 메인에는 요약만 반환되어 메인 컨텍스트가 보호됩니다.
1에이전트 정의 파일 작성

정의 파일은 YAML frontmatter 핵심 4필드(name / description / tools / model, 필수는 name과 description)와 시스템 프롬프트 본문으로 구성됩니다. 프로젝트 루트에서 아래 블록을 실행하세요.

Terminal, 전체 복사
cd ~/claude-lab/ch1
mkdir -p .claude/agents

cat > .claude/agents/code-reviewer.md << 'EOF'
---
name: code-reviewer
description: |
  PR diff를 검토하여 가독성, 안전성, 성능, 테스트 커버리지 관점에서
  개선 사항을 제안하는 시니어 코드 리뷰어. 사용 시점: 코드 리뷰가
  필요하다고 사용자가 요청하거나 git diff 검토가 필요한 경우.
tools: Read, Grep, Glob, Bash(git diff:*), Bash(git log:*)
model: sonnet
---
당신은 10년 경력의 시니어 소프트웨어 엔지니어입니다.

# 검토 우선순위
1. 안전성: null 참조, race condition, 입력 검증 누락
2. 가독성: 명명, 함수 크기, 주석 필요성
3. 성능: 알고리즘 복잡도, 불필요한 I/O
4. 테스트: 새 코드의 테스트 커버리지

# 출력 형식
- Severity: high | medium | low
- 파일:라인, 문제 설명, 권장 수정안
EOF
필드역할이 예시의 선택
name고유 식별자, kebab-case 영문 소문자code-reviewer
description자동 디스패치의 판단 기준, 사용 시점까지 명시리뷰 요청 / git diff 검토 시
tools도구 화이트리스트, Bash는 명령 패턴으로 제한읽기 전용 + git diff / log만
model별칭 sonnet / opus / haiku, 전체 모델 ID, 또는 inherit. 생략 시 inherit(메인 모델 상속)sonnet, 일상 리뷰의 표준
2등록 확인
Terminal
claude
변경 안내, /agents 위저드 제거 (v2.1.198+) 구버전에서 쓰던 /agents는 이제 안내 문구만 출력합니다. 에이전트 관리 자체가 파일 기반으로 바뀐 것은 아니며(파일 위치와 frontmatter는 동일), 확인은 @ 멘션 typeahead 또는 Claude에게 질문으로, 생성/수정은 파일 편집 또는 Claude에게 요청으로 합니다.

입력창에 @를 입력하면 파일 멘션과 함께 code-reviewer (agent)가 typeahead 목록에 나타납니다(Esc로 닫기). 또는 아래처럼 직접 물어봐도 됩니다.

Claude 세션 입력
현재 이 세션에서 사용할 수 있는 서브에이전트 목록을 알려주세요
출력 예시
❯ 현재 이 세션에서 사용할 수 있는 서브에이전트 목록을 알려주세요

● 이 세션에서 사용 가능한 서브에이전트는 7개입니다.

  프로젝트 정의 (.claude/agents/)

  ┌───────────────┬───────────────────────────────────────────────────────────────────────────────────┬─────────────────────────────────────┐
  │     이름      │                                       용도                                        │                도구                 │
  ├───────────────┼───────────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────┤
  │ code-reviewer │ PR diff를 가독성·안전성·성능·테스트 커버리지 관점에서 검토하는 시니어 코드 리뷰어 │ Read, Grep, Glob, git diff, git log │
  └───────────────┴───────────────────────────────────────────────────────────────────────────────────┴─────────────────────────────────────┘

  내장 (built-in)

  ┌───────────────────┬──────────────────────────────────────────────────────────────────┬─────────────────────────────────┐
  │       이름        │                               용도                               │              도구               │
  ├───────────────────┼──────────────────────────────────────────────────────────────────┼─────────────────────────────────┤
  │ general-purpose   │ 복잡한 조사, 코드 검색, 멀티스텝 작업용 범용 에이전트            │ 전체                            │
  ├───────────────────┼──────────────────────────────────────────────────────────────────┼─────────────────────────────────┤
  │ claude            │ 특정 에이전트에 해당하지 않는 모든 작업 (기본값)                 │ 전체                            │
  ├───────────────────┼──────────────────────────────────────────────────────────────────┼─────────────────────────────────┤
  │ Explore           │ 읽기 전용 광범위 탐색 — 여러 파일/디렉터리를 훑어 위치만 찾을 때 │ 읽기 전용 (편집 도구 제외)      │
  ├───────────────────┼──────────────────────────────────────────────────────────────────┼─────────────────────────────────┤
  │ Plan              │ 구현 전략 설계, 단계별 계획·핵심 파일 식별                       │ 읽기 전용                       │
  ├───────────────────┼──────────────────────────────────────────────────────────────────┼─────────────────────────────────┤
  │ claude-code-guide │ Claude Code / Agent SDK / Claude API / Claude Tag 관련 질문 답변 │ Bash, Read, WebFetch, WebSearch │
  ├───────────────────┼──────────────────────────────────────────────────────────────────┼─────────────────────────────────┤
  │ statusline-setup  │ 상태줄(status line) 설정 구성                                    │ Read, Edit                      │
  └───────────────────┴──────────────────────────────────────────────────────────────────┴─────────────────────────────────┘

  현재 이 프로젝트에서 직접 정의한 것은 code-reviewer 하나이고, 나머지는 Claude Code 기본 제공 에이전트입니다. fix/user-plan-default 브랜치에 커밋된 변경이 있으니, 필요하시면
  code-reviewer로 해당 diff 검토를 돌려드릴 수 있습니다.
3첫 호출, 버그 수정 커밋 리뷰
Claude 세션 입력
code-reviewer 에이전트를 사용해서 최근 커밋의 변경분(git diff HEAD~1)을 검토해 주세요
관찰 포인트
● Agent "최근 커밋 diff 코드 리뷰" finished · 1m 6s

● code-reviewer 검토가 끝났습니다.

  변경 요약

  커밋 6762170은 src/userService.js의 getUserPlan을 user.profile.plan 직접 접근에서 user?.profile?.plan ?? "unknown"으로 고친 1줄 수정입니다. git log를 보면 이 코드는 dfa9bfd에 이미
  있었으나 463974d "initial lab project (with bug)"에서 옵셔널 체이닝이 의도적으로 제거되었고, 이번 커밋이 그 버그를 되돌린 것입니다.

  발견 사항

  Critical / Major: 없음

  Minor — src/userService.js:8 의도 설명 주석 소실
  dfa9bfd에 있던 // 사용자나 profile이 없으면 예외 대신 기본값을 반환 주석이 복원되지 않았습니다. 로직은 규약을 지키지만, 왜 옵셔널 체이닝이 필요한지 남겨두면 같은 버그가 재발하는 걸 막는
  데 도움이 됩니다.

  Minor — test.js 실패해도 exit code 0
  console.log로 PASS/FAIL만 출력하고 process.exitCode를 설정하지 않습니다. 실제로 npm test의 exit code가 0임을 확인했습니다 — 실패 케이스가 생겨도 CI가 감지하지 못합니다. 이번 diff가 만든
  문제는 아니지만, "모든 수정에는 테스트 통과가 동반되어야 함" 규약의 실효성을 떨어뜨립니다.

  if (!pass) process.exitCode = 1;

  Nit — 엣지 케이스는 실제로 안전하나 미검증
  getUserPlan(999), getUserPlan(), getUserPlan(null) 모두 "unknown" 반환을 직접 실행해 확인했습니다. 로직은 안전하지만 테스트에 없어 회귀 감지가 안 됩니다.

  규약 준수

  ES Modules, 예외 대신 기본값 반환, users.js 데이터 구조 미변경, 신규 패키지 없음 — 모두 준수. npm test의 Test 1/2/3 전부 PASS.

  테스트 커버리지 누락

  1. 존재하지 않는 id (getUserPlan(999))
  2. 인자 미전달 / null / undefined
  3. profile은 있지만 plan 필드가 없는 경우
  4. 실패 시 CI가 실패로 판정하지 못하는 구조 문제

  종합 의견

  수정 후 승인. 핵심 로직은 정확하고 이번 diff를 막을 이슈는 없습니다. 후속으로 주석 복원, test.js exit code 처리, 누락 케이스 테스트 추가를 권합니다.

  이 세 가지 후속 개선을 지금 적용해 드릴까요?

결과를 받으면 출력 형식이 시스템 프롬프트에서 지정한 Severity 구조를 따르는지, 우선순위 4개 관점이 반영되었는지 확인하세요.

CHECKPOINT
TASK 02

도구 권한 격리 체험

6분

읽기 전용 에이전트에 일부러 권한 밖 요청을 보내 거부 동작을 관찰하고, tools 필드에 권한을 명시적으로 추가한 뒤 새 위임으로 차이를 체험합니다.
최소 권한 원칙과 정의 갱신 타이밍의 실습입니다.

Tools란 무엇인가 서브에이전트 정의의 tools 필드는 그 에이전트가 쓸 수 있는 손발의 목록입니다. Read(파일 읽기), Grep/Glob(검색), Edit/Write(수정과 생성), Bash(명령 실행)처럼 하나하나가 구체적 능력이고, 목록에 없는 도구는 시도 자체가 차단됩니다. 이 Task는 그 울타리를 일부러 밟아 보며 최소 권한이 실제로 동작하는지 확인하는 실험입니다.
1읽기 전용 테스트 에이전트 작성
Terminal, 전체 복사
cat > .claude/agents/dangerous-test.md << 'EOF'
---
name: dangerous-test
description: 의도적 권한 테스트용 에이전트. 사용 시점: 사용자가 이름으로 직접 지정할 때만.
tools: Read, Grep
model: haiku
---
당신은 테스트 에이전트입니다.
파일을 분석하고 결과를 보고하세요.
EOF
2일부러 권한을 벗어난 요청

tools에 Edit이 없는 상태에서 파일 수정을 요청합니다. Claude Code가 .claude/agents/를 감시하므로 파일 저장 후 몇 초 안에 자동 인식됩니다(Task 1에서 디렉토리를 만든 뒤 세션을 시작했기 때문에 재시작 불필요).

Claude 세션 입력
dangerous-test 에이전트를 사용해서 src/users.js의 Park 사용자에게 profile 필드를 추가해 주세요
관찰 포인트
● dangerous-test로는 이 수정을 할 수 없습니다 — 도구가 Read와 Grep뿐이라 파일 편집 능력이 없습니다. 위임하면 편집 단계에서 실패합니다. 이건 판단이 아니라 에이전트 정의상의 제약입니다.

  요청하신 수정 자체는 재확인하신 것으로 보고 제가 직접 진행합니다.

실제 세션이라면 이대로 메인이 직접 고치게 두면 되지만, 이 실습에서는 그 제안을 잠시 보류합니다. 다음 단계에서 에이전트에 Edit 권한을 부여해 재위임하며, 권한 경계가 동작을 어떻게 바꾸는지를 확인하는 것이 목적입니다.

tools 필드 생략의 위험 tools 필드를 아예 생략하면 메인의 모든 도구를 상속합니다(지양). 화이트리스트를 명시하는 습관이 안전의 기본입니다. 반대로 대부분을 상속하되 일부만 빼려면 disallowedTools: Write, Edit처럼 거부 목록을 쓸 수 있습니다.
3권한을 명시적으로 부여
Terminal, tools에 Edit 추가
sed -i.bak 's/^tools: Read, Grep$/tools: Read, Grep, Edit/' .claude/agents/dangerous-test.md
grep '^tools:' .claude/agents/dangerous-test.md   # tools: Read, Grep, Edit 확인
4/exit로 나갔다 들어와 새 위임으로 재시도
중요, 같은 요청을 그냥 반복하면 여전히 거부됩니다 정의 파일 변경은 다음 새 위임부터 적용됩니다. 그런데 메인은 직전 턴에서 "이 에이전트는 수정 불가"를 이미 학습했기 때문에, 같은 요청을 반복하면 재위임 없이 기존 맥락으로 답하거나 이전 인스턴스를 재개(resume)할 수 있습니다. 재개된 서브에이전트는 스폰 시점의 도구 권한을 그대로 유지합니다. /exit로 세션을 완전히 나갔다가 claude로 다시 들어오세요. 갱신된 정의로 새 인스턴스가 뜹니다(같은 세션에서 /clear로 맥락만 비워도 동일 효과).
Claude 세션 입력, 세션 종료
/exit
Terminal (~/claude-lab/ch1), 재진입
claude
Claude 세션 입력, 재시도
dangerous-test 에이전트를 사용해서 src/users.js의 Park 사용자에게 profile 필드를 추가해 주세요
관찰 포인트
[dangerous-test] Read src/users.js
[dangerous-test] Edit src/users.js   # 이번에는 Edit 도구 사용 (승인 요청 표시)
  Park(id 3)에 profile 필드를 추가했습니다.

이것이 의도적 권한 부여와 무분별한 전체 권한의 차이입니다. 동시에, 서브에이전트의 도구 권한이 인스턴스 스폰 시점에 고정된다는 점과 메인의 대화 맥락이 재위임을 가로막을 수 있다는 점, 그리고 /exit 재진입(또는 /clear)이 이런 상황의 실전 해결책이라는 점까지 함께 체득한 것입니다.

5실습 프로젝트 원상 복구
Terminal, 데이터 원복
git checkout -- src/users.js
rm -f .claude/agents/dangerous-test.md .claude/agents/dangerous-test.md.bak
CHECKPOINT
TASK 03

에이전트 3종 완성, test-writer와 docs-writer 추가

7분

Task 4의 디스패치 실습을 위해 test-writer(테스트 작성)와 docs-writer(문서 작성)를 추가합니다.
두 에이전트의 description이 서로 영역이 겹치지 않게 작성된 점에 주목하세요.

1두 에이전트 한 번에 생성
Terminal, 전체 복사
cat > .claude/agents/test-writer.md << 'EOF'
---
name: test-writer
description: |
  기존 코드의 테스트 커버리지를 분석하고 누락된 엣지 케이스, 분기,
  예외 경로에 대한 테스트를 작성. 사용 시점: 커버리지 부족이 확인되거나
  새 함수에 테스트가 필요한 경우.
tools: Read, Grep, Glob, Edit, Write, Bash(npm test:*)
model: sonnet
---
당신은 테스트 자동화 전문가입니다.

# 작업 절차
1. 대상 함수의 시그니처와 분기 구조 분석
2. 기존 테스트 파일이 있다면 스타일 일치
3. 정상 케이스, 엣지 케이스, 예외 케이스 모두 커버
4. 테스트 작성 후 실제로 실행하여 통과 확인

# 작성 원칙
- 테스트 하나가 한 가지만 검증
- 의미 있는 테스트 이름
- AAA 패턴 (Arrange Act Assert)
EOF

cat > .claude/agents/docs-writer.md << 'EOF'
---
name: docs-writer
description: |
  README, API 문서, 아키텍처 결정 문서(ADR)를 코드와 일치하도록 작성하거나
  업데이트. 사용 시점: 신규 기능 추가 후 문서 작성이 필요하거나 기존 문서가
  코드와 불일치할 때.
tools: Read, Grep, Glob, Edit, Write, Bash(git log:*)
model: sonnet
---
당신은 기술 문서 작성가입니다.

# 문서 종류별 가이드
- README: 프로젝트 개요, 설치, 사용법, 예시
- API 문서: 함수 시그니처, 파라미터, 반환값, 예시
- ADR: Context, Decision, Consequences 형식

# 작성 원칙
- 코드와 실제 동작이 일치해야 함
- 예시는 실제로 실행 가능한 것
- 한국어와 영어 혼용 가능, 일관성 유지
EOF

ls .claude/agents/
23종 등록 확인

세션 입력창에 @를 입력해 typeahead에 3종 모두 (agent)로 표시되는지 확인하세요. 직접 @agent-test-writer처럼 입력해도 멘션이 인식됩니다. 또는 아래처럼 물어봐도 됩니다.

Claude 세션 입력
사용 가능한 서브에이전트를 모두 알려주세요
출력 예시
프로젝트 서브에이전트 (.claude/agents/):
  - code-reviewer : PR diff 검토
  - test-writer   : 테스트 커버리지 확장
  - docs-writer   : 문서 작성과 동기화
description이 디스패치 정확도를 결정한다 메인은 사용자 요청과 각 에이전트의 description을 대조해 자동 선택합니다. "코드를 분석하는 도우미"처럼 모호하거나 "모든 개발 작업을 돕는 만능"처럼 광범위하면 잘못된 디스패치가 발생합니다. 구체적 역할 + 사용 시점을 명시하고, 에이전트끼리 경계를 겹치지 않게 하세요.
이 정의 파일들은 Git으로 팀과 공유됩니다 .claude/agents/는 프로젝트 단위(팀 공유, Git 커밋 대상), ~/.claude/agents/는 개인 전역(모든 프로젝트에서 사용)입니다. 지금 만든 3종을 커밋해 두면 팀 전체가 동일한 에이전트를 사용합니다.
CHECKPOINT
TASK 04

Task 디스패치 3패턴

10분

같은 에이전트들을 자동 디스패치, 명시적 호출, 병렬 호출 3가지 방식으로 부려 보고, /cost로 에이전트별 비용을 비교합니다.
각 패턴 사이에 /exit로 나갔다 들어오거나 /clear로 컨텍스트를 정리하면 관찰이 깨끗해집니다.

현재 버전 참고, Agent 도구와 백그라운드 실행 위임에 쓰이는 내부 도구 이름이 Task에서 Agent로 변경되었습니다(구 Task 표기는 별칭으로 동작). 또한 서브에이전트는 기본적으로 백그라운드로 실행됩니다. 진행 상황은 프롬프트 아래 패널과 /tasks에서 확인하고, 권한 요청은 메인 세션에 표시되며, Ctrl + B로 실행 중 작업을 백그라운드로 보낼 수 있습니다.
1패턴 A, 자동 디스패치

Task 1에서 했던 것과 같은 리뷰 요청입니다. 다른 것은 위임의 형태입니다. 이번에는 백그라운드로 흘러가고, 메인이 리뷰어의 주장을 직접 재검증하는 것까지 관찰합니다.

에이전트를 지정하지 않고 요청합니다. 메인이 description을 대조해 스스로 code-reviewer를 선택하는지 관찰하세요.

Claude 세션 입력
이 프로젝트의 최근 변경 사항을 코드 리뷰해 주세요
관찰 포인트 및 출력 예시
● 테스트 실행과 파일 확인을 마쳤고, code-reviewer 서브에이전트가 diff 리뷰를 진행 중입니다. 결과가 오면 정리해서 전달하겠습니다.

  현재까지 확인한 사실:
  - 변경은 src/userService.js:9 한 줄 (user.profile.plan → user?.profile?.plan ?? "unknown")
  - npm test 3개 케이스 모두 PASS
  - 참고: 루트의 report.json은 이 프로젝트의 다른(더 진행된) 상태를 담은 이전 세션 산출물이라 현재 코드와 맞지 않습니다 — 리뷰 대상에서 제외했습니다.

✻ Waiting for 1 background agent to finish

● Agent "Review latest commit diff" finished · 1m 13s

● Bash(cd /home/ec2-user/claude-lab/ch1
      echo "=== branch topology ==="…)
  ⎿  === branch topology ===
     * 6762170 (HEAD -> fix/user-plan-default) fix: handle users without profile
     * 463974d chore: initial lab project (with bug)
     … +13 lines (ctrl+o to expand)

● Bash(git show 463974d -- src/userService.js | tail -20)
  ⎿  commit 463974d0d1e624b46da087f99cfa271fee249f99
     Author: lab <lab@example.com>
     Date:   Thu Sep 10 14:20:10 2026 +0000
     … +15 lines (ctrl+o to expand)

● code-reviewer 리뷰가 끝났고, 주요 주장은 직접 검증했습니다.

  리뷰 대상

  fix/user-plan-default HEAD (6762170), src/userService.js:9 한 줄:

   export function getUserPlan(id) {
     const user = getUser(id);
  -  return user.profile.plan;
  +  return user?.profile?.plan ?? "unknown";
   }

  결론: 이 변경 자체는 올바릅니다. getUser()는 find()라서 없는 id에 undefined를 반환하고, id 3(Park)은 profile이 없습니다. 옵셔널 체이닝이 두 경로를 모두 덮고, ??(|| 아님)라 빈 문자열
  플랜도 보존됩니다. CLAUDE.md의 "예외 대신 기본값 반환" 컨벤션에 부합하며 테스트 3건 모두 PASS입니다.

  지적 사항 (우선순위 순)

  1. (high) npm test가 실패해도 exit code 0 — 검증했습니다

  test.js:3-6의 check()는 결과를 console.log로만 찍고 실패를 집계하지 않습니다. 코드를 일부러 깨서 확인한 결과:

  Test 1 - pro 플랜 사용자: FAIL (got BROKEN)
  Test 2 - free 플랜 사용자: FAIL (got BROKEN)
  Test 3 - profile 없는 사용자: FAIL (got BROKEN)
  npm test EXIT CODE = 0        ← 전부 FAIL인데 성공

  CLAUDE.md는 "모든 수정에는 test.js의 테스트 통과가 동반되어야 함"을 요구하지만, 이 하네스로는 사람이 로그를 눈으로 읽지 않으면 회귀를 절대 못 잡습니다. CI 게이트를 걸어도 무의미합니다.
  실패 카운트 후 process.exitCode = 1, 그리고 fn()을 try/catch로 감싸는 것(지금은 예외가 나면 나머지 테스트가 아예 실행되지 않고 중단)이 다른 어떤 개선보다 먼저입니다.

  2. (medium) 실제로 이 버그는 한 번 재도입된 이력입니다 — 히스토리 확인했습니다

  6762170  fix: handle users without profile          ← 지금 이 수정
  463974d  chore: initial lab project (with bug)      ← 수정을 되돌림
  dfa9bfd  fix: profile 없는 사용자의 ... 기본값 반환   ← 동일한 수정이 이미 있었음
  6819730  chore: initial lab project (master)

  463974d는 dfa9bfd의 옵셔널 체이닝과 설명 주석을 통째로 user.profile.plan으로 원복시켰습니다. 즉 같은 한 줄을 두 번 고친 셈이고, 1번 항목(테스트가 실패를 못 알림)이 바로 이런 재도입이
  조용히 통과하는 이유입니다.

  3. (medium) "존재하지 않는 id" 테스트 없음

  이번 변경의 user?. 부분이 정확히 그 경로인데 테스트가 없습니다. test.js에 추가:

  check("Test 4 - 존재하지 않는 사용자", () => getUserPlan(999), "unknown");

  4. (low) 원래 있던 주석이 함께 사라졌습니다

  dfa9bfd의 // 사용자나 profile이 없으면 예외 대신 기본값을 반환을 복원하면, ?.가 두 군데인 이유(user 부재 + profile 부재)가 드러납니다.

  5. (low) "unknown"이 두 상태를 뭉갭니다

  "id가 틀림"과 "마이그레이션 미완료 사용자"가 구분되지 않습니다. 컨벤션상 기본값 반환은 맞으니 지금 고칠 필요는 없지만, 호출자가 구분해야 하면 getUser(id)로 존재 여부를 먼저 확인하는
  패턴이 필요합니다. 매직 문자열을 상수로 export하는 것도 고려할 만합니다.

  6. (참고) report.json이 커밋되어 있습니다

  463974d에 포함된 에이전트 실행 로그(세션 ID, 토큰/비용)입니다. 게다가 내용이 현재 코드와 맞지 않는 다른 상태(removeUser, isActive 등이 있는 버전)를 담고 있어 혼동을 유발합니다. .gitignore
  처리 또는 삭제를 권합니다.

  ---

  성능/동시성 측면은 문제 없습니다(동기, 메모리 내 3건, find() O(n)은 이 규모에서 무해).

  수정은 아직 하지 않았습니다. 1번(테스트 exit code) + 3번(누락 테스트) + 4번(주석) 을 한 번에 적용하는 게 비용 대비 효과가 가장 큽니다 — 진행할까요?
2패턴 B, 명시적 호출

이름으로 직접 지정하면 메인은 다른 에이전트를 검토하지 않고 예측 가능하게 해당 에이전트를 호출합니다.

Claude 세션 입력
test-writer 에이전트를 사용해서 src/userService.js의 테스트 커버리지를 분석하고, 존재하지 않는 사용자 id 같은 누락된 엣지 케이스 테스트를 추가해 주세요
관찰 포인트 및 출력 예시
● test-writer 에이전트를 띄웠습니다. 전달한 작업 범위:

  - 커버리지 공백 3건: 존재하지 않는 id(getUserPlan(999)), getUser() 직접 테스트 전무, 잘못된 입력(null/인자 없음/문자열 id)
  - 하네스 수정: 실패 집계 후 process.exitCode = 1, try/catch로 예외도 FAIL 처리, 요약 출력 — 이게 없으면 새로 추가한 테스트도 회귀를 못 잡습니다
  - 제약 전달: ESM만, 새 패키지 금지, users.js 구조·userService.js 동작 불변(테스트 추가만)
  - 새 테스트가 실패하면 프로덕션 코드를 고쳐 맞추지 말고 그대로 보고하도록 지시했습니다 (실제 버그 가능성)

  결과 오면 정리해서 전달하겠습니다. 파일 충돌을 피하려고 저는 test.js를 건드리지 않고 대기합니다.

test-writer가 테스트를 추가하고 npm test를 직접 실행해 통과를 확인하는지 지켜보세요(시스템 프롬프트의 작업 절차 4번). 특정 에이전트 실행을 보장하고 싶다면 자연어 대신 @를 입력해 typeahead에서 test-writer (agent)를 선택하는 @-멘션 방식을 쓸 수 있습니다.

3패턴 C, 병렬 호출

독립적인 3개 작업을 한 번에 요청합니다. 메인이 3개의 Task를 동시에 발행하고, 총 시간은 합산이 아니라 가장 오래 걸린 작업 기준(max)이 됩니다.

Claude 세션 입력
이 코드베이스를 종합 점검해 주세요. 다음 세 가지를 병렬로 진행해 주세요.
1) 코드 품질 리뷰
2) 테스트 커버리지 분석
3) README 문서 초안 작성
관찰 포인트
[메인] 3개의 Subagent를 병렬로 호출합니다.
  → Agent(code-reviewer, ...)
  → Agent(test-writer, ...)
  → Agent(docs-writer, ...)
[reviewer] 분석 중... 이슈 2건
[tester  ] 분석 중... 엣지 케이스 1건 보강
[docs    ] README.md 초안 작성 중...
[메인] 결과 종합: 코드 이슈 2건, 테스트 1건 추가, README 초안 완료.
4비용 확인

현재 버전에서 /cost는 /usage의 별칭입니다. 세션 사용량과 어떤 서브에이전트가 한도를 소비했는지 확인할 수 있습니다.

Claude 세션 입력
/cost
출력 예시 (개념, 실제 표시 형식은 버전에 따라 다름)
Current session cost: $0.42
Breakdown:
  Main agent    (Sonnet): $0.09
  code-reviewer (Sonnet): $0.11
  test-writer   (Sonnet): $0.13
  docs-writer   (Sonnet): $0.09
병렬화의 트레이드오프 병렬 호출은 시간은 max로 단축되지만 비용은 합산됩니다. 강의 예시 기준 단일 $0.23 대비 3개 병렬 약 7배까지 증가할 수 있습니다. 반복적인 단순 작업은 model: haiku로 지정해 비용을 조절하고, Opus는 심층 분석에만 사용하세요.
2026.08 - 09 업데이트, 위임이 더 투명해졌습니다 /tasks와 에이전트 상세에 서브에이전트별 모델과 effort가 표시되고(v2.1.243+), maxTurns 한도에서 멈춘 서브에이전트는 결과를 partial로 표시하며 SendMessage로 이어갈 수 있습니다(v2.1.246+).
5서브에이전트 기본 모델 지정 (v2.1.251+)

CLAUDE_CODE_SUBAGENT_MODEL은 이제 기본값으로 동작합니다. 에이전트 정의의 model:이나 호출 시 지정이 있으면 그것이 우선하고, 없을 때만 이 값이 쓰입니다. 리뷰나 문서화처럼 가벼운 위임을 저비용 모델로 돌리는 기본선을 만들 수 있습니다.

Terminal
export CLAUDE_CODE_SUBAGENT_MODEL=global.anthropic.claude-haiku-4-5-20251001-v1:0
claude
Claude 세션 입력
docs-writer 에이전트로 README 요약을 갱신해 주세요. 끝나면 /tasks에서 이 위임이 어떤 모델로 돌았는지 확인합니다.
6리뷰 지적 반영, report.json 추적 제외

패턴 A의 리뷰가 지적한 마지막 항목을 바로 처리합니다. 에이전트 실행 로그(세션 ID, 토큰, 비용)가 담긴 report.json은 코드가 아니므로 저장소에서 빼는 것이 맞습니다. 리뷰 결과를 실제 조치로 닫는 습관까지가 이 태스크의 마무리입니다.

Terminal
git rm --cached report.json 2>/dev/null
echo "report.json" >> .gitignore
git add .gitignore && git commit -m "chore: agent 실행 로그 추적 제외"
CHECKPOINT
TASK 05

Headless 통합과 CI 맛보기

7분

서브에이전트를 대화 없이 스크립트에서 호출하고 JSON으로 후처리합니다.
마지막으로 같은 패턴이 GitHub Actions에서 어떻게 동작하는지 확인합니다.
세션 밖 터미널에서 실행하세요.

1헤드리스로 에이전트 호출
Terminal
git diff HEAD~1 | claude -p \
  "code-reviewer 에이전트로 이 diff를 검토해 주세요" \
  --output-format json > review-result.json

head -c 400 review-result.json
2JSON 후처리
Terminal
# jq가 있다면: 리뷰 본문은 result 키에, 실행 메타데이터는 최상위 키에 있습니다
jq -r '.result' review-result.json
jq '{cost_usd: .total_cost_usd, turns: .num_turns, session: .session_id, output_tokens: .usage.output_tokens}' review-result.json

# high severity 항목만 보려면 본문 텍스트에서 필터
jq -r '.result' review-result.json | grep -i -A2 'high'

# jq가 없다면
python3 -c 'import json; print(json.load(open("review-result.json"))["result"])'
JSON 출력의 실제 구조 --output-format json의 최상위 키는 result, session_id, num_turns, total_cost_usd, usage, is_error 같은 실행 메타데이터이고, 에이전트가 쓴 리뷰 본문은 result 문자열 하나에 들어갑니다. summary나 issues 같은 키는 없습니다. 필드 단위로 다루려면 --json-schema '<스키마>'를 함께 주세요. 스키마에 맞춘 결과가 structured_output 키에 담기므로 jq '.structured_output'으로 읽습니다. 스키마 없이 프롬프트로 JSON 출력을 요구했다면 jq -r '.result | fromjson'으로 한 번 더 파싱합니다. Chapter 5 강의에서 --json-schema 기반의 계약형 파이프라인을 다룹니다.
3CI 통합 패턴 확인 (참고)

같은 명령을 GitHub Actions에 넣으면 PR마다 자동 리뷰가 됩니다. 아래는 강의에서 다룬 직렬(리뷰) + 병렬(테스트와 보안) 복합 패턴의 워크플로입니다. GitHub 저장소가 있다면 push해서 실험해 보세요. 선택 과제입니다.

.github/workflows/pr-check.yml, 참고용
name: PR Check with Subagents
on: pull_request

jobs:
  comprehensive-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - run: npm install -g @anthropic-ai/claude-code

      # 직렬: code-reviewer 먼저 실행 후 결과 보고
      - name: Code Review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          claude -p "code-reviewer 에이전트로 git diff origin/main..HEAD를 검토해 주세요" \
            --output-format text > review.md

      # 병렬 요청: 테스트와 보안을 한 번에
      - name: Tests + Security
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          claude -p "다음을 모두 점검해 주세요: 1) 테스트 커버리지 부족 2) 보안 이슈" \
            --output-format json > result.json

      # 결과를 PR 코멘트로 게시
      - uses: peter-evans/create-pr-comment@v1
        with: { body-path: review.md }
CI에서의 안전장치 무인 실행에서는 API Key를 반드시 Secrets로 주입하고, 읽기 전용 검사라면 --allowed-tools "Read,Grep,Glob"으로 도구를 제한하세요. Chapter 5(CLI Reference)에서 권한 플래그와 파이프라인 패턴을 심화합니다.
CHECKPOINT

마무리, 학습 목표 체크리스트

Chapter 2의 학습 목표를 스스로 점검하세요. 미달성 항목은 강의 자료의 해당 Part를 다시 확인하거나 Slack 채널에 질문을 남기세요.

목표확인 질문관련
ConceptContext Isolation과 위임의 가치를 설명할 수 있는가강의 Part 1
DefineYAML frontmatter 4필드로 에이전트를 정의했는가Task 1
Least Privilegetools 화이트리스트로 권한 거부와 부여를 체험했는가Task 2
Description자동 디스패치가 description 기반임을 확인했는가Task 3, 4
Dispatch자동 / 명시 / 병렬 3패턴을 모두 실행했는가Task 4
Cost/cost로 에이전트별 비용과 병렬 트레이드오프를 확인했는가Task 4
Automation헤드리스 + JSON으로 CI 통합 패턴을 실행했는가Task 5
NEXT CHAPTER

Chapter 3 - Admin Setup

Enterprise 환경 배포의 모든 것. 사내 배포 전략(npm 미러, 대량 배포), 자격증명 관리(Bedrock, SSO, IAM), 네트워크와 보안(프록시, 화이트리스트), 거버넌스(조직 정책, 사용량 모니터링)를 다룹니다.

Claude Code Deep Dive Workshop, Chapter 2 Hands-on Lab
기준: Claude Code 2.1.267, 2026.09 / 명령어와 출력은 버전에 따라 일부 다를 수 있습니다

READING CHECK

이 페이지를 읽으셨나요?

읽음 표시는 실습 체크포인트와 별도로 관리됩니다. 아직 읽지 않음

이 페이지에서맨 위로 ↑