Settings, 나의 Claude Code를 팀의 플랫폼으로
이번 랩은 두 개의 파트로 구성됩니다. Part A (40분)는 설정 계층, 권한 패턴, Hooks, MCP 연결을 태스크 방식으로 구현하고, Part B 슈퍼랩 (40분)은 배운 것으로 실제 팀 에셋 (커스텀 커맨드, 스킬, 스타터 킷)을 직접 빌드합니다. 슈퍼랩의 규칙은 하나, 파일을 직접 코딩하지 말고 Claude에게 시켜서 만드세요.
이 챕터의 진행 흐름
개요
앞의 다섯 Task(A1~A5)는 각각 설정, 권한, 모드, Hook, MCP라는 기본 구성을 하나씩 손에 넣는 과정이고,
이어지는 40분 슈퍼랩(M1~M4)은 그 구성들을 조립해 팀에 배포 가능한 스타터 킷을 만드는 과정입니다.
지금 어느 단계에 있는지 진행 중 위치가 헷갈리면 이 흐름도로 돌아오세요.
상세 흐름도 펼쳐 보기
사전 준비 확인
2분
이후 실습에서 사용할 프로젝트를 만듭니다. Hook 스크립트는 jq를 사용하고, 없으면 Python 3로 JSON을 읽습니다. 둘 중 하나를 사용할 수 있는지 확인하세요.
if command -v jq >/dev/null 2>&1; then
jq --version
elif command -v python3 >/dev/null 2>&1; then
python3 --version
else
echo "jq 또는 Python 3를 설치한 뒤 진행하세요."
fi
mkdir -p ~/claude-lab/ch4/src && cd ~/claude-lab/ch4
cat > package.json << 'EOF'
{
"name": "settings-lab",
"version": "1.0.0",
"type": "module",
"scripts": { "test": "node test.js" }
}
EOF
cat > src/greet.js << 'EOF'
export function greet(name) {
return `Hello, ${name}!`;
}
EOF
cat > test.js << 'EOF'
import { greet } from "./src/greet.js";
console.log(greet("Claude") === "Hello, Claude!" ? "PASS" : "FAIL");
EOF
cat > .env << 'EOF'
# 실습용 가짜 값
API_TOKEN=lab-fake-token
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: settings lab scaffold"
echo "// greeting flair" >> src/greet.js && git commit -qam "feat: add greeting flair"
npm test
설정 계층, 팀 설정과 개인 설정의 공존
10분
같은 프로젝트에서 팀 공유 설정(.claude/settings.json, Git 커밋 대상)과
개인 설정(.claude/settings.local.json, 커밋 제외)을 나눠 쓰고
병합 결과를 확인합니다.
HOME - 전역 (모든 프로젝트)
~/.claude/ # 개인 기본값
└─ settings.json, CLAUDE.md, rules/, skills/, agents/, plugins/
~/.claude.json # 디렉토리 밖 파일 - 앱 상태, 신뢰 승인 기록
REPO - 저장소 (이 프로젝트만)
your-project/
├─ CLAUDE.md # 매 세션 로드 지침 (.local.md는 개인)
├─ .mcp.json # 팀 공유 MCP 서버 - .claude 밖, 루트에 위치!
└─ .claude/
├─ settings.json # ★ 팀 공유 - Git 커밋 대상 (이 랩 Step 1)
├─ settings.local.json # ★ 개인 - 커밋 제외, Git 제외 여부 확인 (이 랩 Step 2)
├─ rules/ # 경로 조건부 지침 조각 (paths:)
├─ skills/, commands/, agents/, workflows/
└─ output-styles/, agent-memory/| 우선순위 | 스코프 | 파일 | /status 표기 | 용도 |
|---|---|---|---|---|
| 1 | Managed | OS 시스템 경로 또는 원격 | Enterprise managed settings | 조직 강제, 사용자가 못 덮음 |
| 2 | CLI 인자 | --settings 등 | - | 세션 한정 오버라이드 |
| 3 | Local | .claude/settings.local.json | Project local settings | 개인 실험, 커밋 제외 |
| 4 | Project | .claude/settings.json | Shared project settings | 팀 표준, Git 커밋 |
| 5 | User | ~/.claude/settings.json | User settings | 개인 전역 기본값 |
cd ~/claude-lab/ch4
mkdir -p .claude
cat > .claude/settings.json << 'EOF'
{
"model": "sonnet",
"permissions": {
"allow": ["Bash(npm test *)"],
"ask": ["Edit(**)", "Bash(git push *)"],
"deny": [
"Read(.env*)", "Edit(.env*)",
"Bash(rm *)", "Bash(curl *)", "Bash(wget *)",
"WebFetch"
]
}
}
EOF
echo "팀 설정 완료"
개인적으로 자주 쓰는 명령을 팀 파일에 접근하지 않고 허용해 봅니다.
cat > .claude/settings.local.json << 'EOF'
{
"permissions": {
"allow": ["Bash(npm run *)"]
}
}
EOF
echo "개인 설정 완료"
검증은 랩 폴더 안에서 해야 프로젝트 설정이 로드됩니다. 경로부터 맞추고 세션을 엽니다.
cd ~/claude-lab/ch4
claude --permission-mode default
A1·A2는 승인 화면을 비교하므로 default 모드로 시작합니다. 기존 허용 규칙이나 조직 정책에 따라 결과가 다르면 /permissions에서 규칙과 출처를 확인하세요.
/status
Setting sources: Shared project settings, Project local settings, …
# Shared project settings = .claude/settings.json
# Project local settings = .claude/settings.local.json
# User·Managed 등 추가 소스와 표시 형식은 환경에 따라 다를 수 있습니다.
개인 설정에는 지속 승인으로 생성한 규칙도 저장할 수 있습니다. 다음 요청에서 승인 화면이 나타나면 저장 범위를 확인하고 진행합니다.
npm ls로 의존성 목록을 보여주세요
승인 화면에서 “다시 묻지 않기” 계열의 지속 승인 옵션과 이 프로젝트의 개인 설정 범위를 선택하세요. 이미 허용돼 화면이 나오지 않으면 /permissions에서 해당 규칙의 출처를 확인합니다.
cat ~/claude-lab/ch4/.claude/settings.local.json
"allow": ["Bash(npm run *)", "Bash(npm ls *)"]
# 지속 승인 뒤의 저장 예시입니다.
# 실제 생성된 규칙과 저장 범위는 파일에서 확인합니다.
/permissions
allow 목록에서 팀 규칙(npm test)과 개인 규칙(npm run)이 함께 적용되는지 확인합니다. 지속 승인을 저장했다면 생성된 규칙과 그 출처도 확인하세요. 권한 규칙은 스코프 간 덮어쓰기가 아니라 병합되며, deny는 어느 스코프에 있든 우선합니다.
.claude/settings.json은 팀과 공유하고, .claude/settings.local.json은 Git 공유 대상에서 제외합니다.
git check-ignore -v -- .claude/settings.local.json으로 제외 여부를 확인하고, 제외되지 않았다면 .gitignore에 추가하세요.
설정 항목에 따라 반영 시점이 다를 수 있습니다. 현재 모델을 바꿀 때는 /model을 사용하세요.
공식 문서: 권한 모드
권한 규칙, allow / ask / deny 3단 대조
11분A1에서 작성한 규칙으로 allow는 무확인 실행, ask는 승인 요청, deny는 즉시 차단이라는 세 가지 동작을 같은 세션에서 연속으로 대조 관찰합니다.
| 패턴 | 매칭 | 주의 |
|---|---|---|
| BASH 패턴 | ||
Bash(npm run build) | 정확히 이 명령만 | 와일드카드가 없으면 exact |
Bash(npm test *) | npm test로 시작하는 명령 | 공백 + *는 단어 경계. Bash(ls *)는 lsof를 안 잡지만 Bash(ls*)는 잡음 |
Bash(* install) | install로 끝나는 명령 | 와일드카드는 앞, 중간, 뒤 어디든 올 수 있음 |
Bash(npm test:*) | 위 Bash(npm test *)와 동등 | :*는 패턴 끝에서만 인식. Bash(git:* push)의 콜론은 리터럴 |
| 경로 패턴 , gitignore 스타일 | ||
Read(./.env) | cwd 바로 아래의 .env | ./ = 현재 디렉토리 기준 |
Edit(/src/**) | 이 규칙이 적힌 설정 파일 위치 기준의 src 하위 | / 하나는 절대 경로가 아닙니다! 프로젝트 설정이면 프로젝트 루트, user 설정이면 ~/.claude 기준 |
Read(//etc/passwd) | 파일시스템 절대 경로 | 진짜 절대 경로는 // 두 개 |
Edit(src/**) | allow면 <cwd>/src만deny/ask면 모든 깊이의 src | 경로가 들어간 단일 세그먼트는 규칙 종류에 따라 깊이가 달라짐 |
*는 한 경로 구간 안, **는 하위 디렉토리 전체, 심링크: allow는 링크와 대상 양쪽 규칙 필요, deny는 한쪽만 걸려도 차단 | ||
timeout, time, nice, nohup, 플래그 없는 xargs는 규칙 매칭 전에 제거됩니다.
그래서 Bash(npm test *)가 timeout 30 npm test도 커버합니다.
npx, docker exec, devbox run은 규칙 매칭 과정에서 제거되지 않습니다.
그래서 Bash(devbox run *)는 devbox run rm -rf .를 허용합니다. 러너를 허용할 때는 내부 명령까지 포함한 규칙을 쓰세요.복합 명령:
&&, ||, ;, |, &, 개행으로 나뉜 각 서브명령이 독립적으로 매칭되어야 합니다.
명령 패턴은 해석 방식과 허용 범위를 함께 검토해야 합니다. 명시적 deny를 Hook의 allow로 해제할 수는 없습니다. 내용에 따른 추가 검사는 PreToolUse Hook으로 설계하고 권한 규칙과 함께 확인하세요.
npm test를 실행해 주세요
승인 프롬프트 없이 바로 실행되고 PASS가 출력됩니다. allow 규칙 Bash(npm test *)에 매칭되었기 때문입니다.
curl https://example.com 을 실행해서 응답을 보여주세요
Ran 1 shell command
→ curl 실행 권한이 거부되어 요청을 보내지 못했습니다.
# 승인 기회 자체가 없습니다. .env 읽기 요청도 동일하게 차단됩니다
# 거부 사실이 Claude에게 전달되므로, Claude가 대안을 제안할 수 있습니다
# 제안 내용은 세션마다 다릅니다. 따라가지 말고 다음 단계로 넘어가세요
Bash(curl *)): 해당 명령 패턴에 맞는 Bash 호출을 거부합니다. 거부 사실을 받은 Claude가 다른 방법을 제안할 수 있습니다.괄호 없는 도구 이름(
WebFetch): 해당 도구를 세션에서 사용할 수 있는 도구 목록에서 제외합니다.이 규칙들은 curl·wget·WebFetch라는 특정 접근 경로를 제어합니다. 다른 프로그램의 네트워크 사용까지 제한하려면 샌드박스 등 별도의 네트워크 경계가 필요합니다.
src/greet.js의 인사말을 "Hi"로 바꿔 주세요
Edit은 ask 규칙이므로 diff 미리보기와 승인 프롬프트가 표시됩니다. 승인해서 진행하세요.
이번 편집을 승인한 것과 지속적인 파일 권한 규칙을 추가한 것은 다릅니다.
승인 범위는 화면에서 확인하고, 지속적으로 적용할 파일 권한은 settings.json의 permissions에 명시해 관리합니다.
공식 문서: 권한 규칙
Permission mode 이해와 Auto 복사 정책
5분기본 모드를 이해하고, 작업 폴더 밖으로 파일을 복사하지 않는 규칙을 Auto 세션에 적용합니다.
규칙 등록을 확인하고, 도구별 실행 결과와 최종 파일 생성 여부를 함께 살펴봅니다.
| 모드 | 규칙에 매칭되지 않은 호출 | 쓰는 자리 |
|---|---|---|
default | 승인이 필요한 호출은 사용자에게 확인 | CLI의 Manual 모드, 직접 승인하며 작업 |
acceptEdits | 파일 편집과 일부 파일 관리 명령 자동 승인 | 반복적인 코드 편집 |
plan | 읽기·조사 중심, 소스 편집 제한 | 수정 전 분석과 계획. /plan |
auto | 분류기가 요청과 행동을 검토해 허용·차단 | 승인 요청을 줄이면서 작업 |
Enterprise와 Amazon Bedrock의 기본 시작 모드는 Manual입니다. 이번에는
deny-rule.json의 permissions.defaultMode: "auto"로 모드를 지정하고, --settings로 파일을 읽습니다./exit로 나온 뒤 시작합니다. 준비는 일반 셸 명령으로 진행하며, deny-rule.json을 그대로 읽어 한 세션에서 확인합니다. 실습은 전용 폴더에서 진행합니다.cd ~/claude-lab/ch4
claude auto-mode configclaude auto-mode defaultsconfig는 현재 적용 기준, defaults는 내장 기본 기준입니다. 네 필드의 역할을 확인하세요.
| 필드 | 확인할 내용 |
|---|---|
environment | 작업 환경과 신뢰 범위 |
allow | 분류기 내부의 허용 예외 |
soft_deny | 사용자 의도와 항목별 예외 조건을 함께 평가하는 차단 기준 |
hard_deny | 분류기 내부에서 사용자 의도나 allow 예외로 해제하지 않는 차단 기준 |
전용 실습 폴더에 work와 export를 만듭니다. 준비할 파일은 sample.txt와 deny-rule.json 두 개입니다. Claude는 work에서 시작하며, 샘플은 개인정보나 비밀값이 없는 일반 텍스트입니다.
~/claude-lab/a3-copy/
├── work/ ← Claude의 시작 작업 폴더
│ ├── sample.txt ← 복사 대상
│ └── deny-rule.json ← --settings로 읽을 설정
└── export/ ← 작업 폴더 밖의 복사 목적지아래 코드는 두 파일을 준비하고, 재실행 시 이전 실습의 local-copy.txt와 export/copied.txt만 정리합니다.
mkdir -p ~/claude-lab/a3-copy/work ~/claude-lab/a3-copy/export &&
cd ~/claude-lab/a3-copy/work &&
rm -f -- local-copy.txt ../export/copied.txt &&
printf 'A3 copy policy sample.\nNo personal or secret data.\n' > sample.txt &&
cat > deny-rule.json <<'JSON'
{
"permissions": {
"defaultMode": "auto"
},
"sandbox": {
"enabled": false
},
"autoMode": {
"classifyAllShell": true,
"hard_deny": [
"$defaults",
"작업 폴더 외부 복사 금지: 이 세션을 시작한 작업 폴더(work) 안의 파일을 그 폴더 밖으로 복사하는 행위를 금지한다. 사용자가 명시적으로 요청해도 허용하지 않는다. 파일 내용 읽기와 시작 작업 폴더 안에서의 복사는 이 금지 규칙의 대상이 아니다."
]
}
}
JSONAuto 분류기의 차단을 별도로 확인하기 위해, 이번 실습 세션에서만 샌드박스를 끕니다.
classifyAllShell: true는 Auto 모드에서 Bash·PowerShell allow 규칙의 사전 허용을 중단하고 심사 대상 셸 명령을 분류기로 보냅니다. 명시적 deny·ask는 먼저 적용됩니다.
Read·Write·Edit까지 모두 분류기로 보내는 설정은 아닙니다. Bash의 거부와 후속 도구의 처리, 최종 파일 결과를 각각 확인하세요.
autoMode는 Project·Local 설정 파일에서는 읽지 않으므로 이번 실습은 --settings로 전달합니다.
추가한 규칙은 이 세션을 시작한 work 폴더를 기준으로 합니다. "$defaults"는 hard_deny의 내장 기준을 유지합니다. deny-rule.json을 --settings로 읽으므로 전역 설정 파일을 편집하지 않습니다.
claude --settings ./deny-rule.jsonAuto 모드로 시작했는지 확인하고 다음을 입력합니다.
/permissionsAuto mode 탭에서 Hard deny · flag 항목을 찾고, 실제로 로드된 규칙과 출처를 확인합니다.
| 확인할 항목 | 의미 |
|---|---|
| Hard deny의 Built-in rules | $defaults로 내장 기본 기준을 유지했는지 확인 |
| Hard deny · flag의 외부 복사 금지 문장 | deny-rule.json의 커스텀 규칙이 로드됐는지 확인 |
| From the --settings flag | 규칙을 읽은 출처가 이번 실행의 --settings인지 확인 |
항목을 열어 규칙 원문과 출처를 확인합니다. 이 확인은 규칙 등록 확인이며, 실제 작업 결과는 다음 단계에서 별도로 살펴봅니다.
다음 요청을 하나씩 입력하고 응답을 확인합니다.
Bash 도구로 cp sample.txt local-copy.txt를 실행해 주세요.Bash 도구로 cp sample.txt ../export/copied.txt를 실행해 주세요.외부 복사 요청이 끝나면 Ctrl+O로 도구 호출을 펼칩니다. Bash의 결과만 보지 말고, 이후 Write 등 다른 도구가 호출됐는지와 최종 파일 결과를 확인하세요. 추가 도구 사용을 따로 지시할 필요는 없습니다.
| 직접 확인할 항목 | 기록할 내용 |
|---|---|
| Auto mode 탭의 Hard deny · flag | 커스텀 규칙이 세션에 등록됨 |
Bash의 Denied by auto mode classifier | 해당 Bash 호출이 거부됨 |
| Write 등 후속 도구의 실행 결과 | 다른 도구를 통해 목적지 파일이 생성됐는지 확인 |
diff 결과와 IDENTICAL | 외부 파일 내용이 원본과 같은지 확인 |
/exitls -l ../export/
if [ -f ../export/copied.txt ]; then
diff sample.txt ../export/copied.txt &&
echo "IDENTICAL"
else
echo "외부 파일 없음"
ficopied.txt가 있고 IDENTICAL이 출력되면 외부에 원본과 같은 파일이 생성된 것입니다. 파일이 없으면 앞의 도구 결과와 함께 거절·차단 여부를 확인합니다.
공식 문서: Auto 분류기 설정
PostToolUse Hook, 편집 뒤 자동 검사
10분Edit·Write로 파일을 편집한 뒤 구문 검사를 실행하는 Hook을 구성합니다. PreToolUse가 실행 전 제어라면, PostToolUse는 실행 후 검사와 피드백입니다. 등록·스크립트 단독 검사·실제 편집 이후의 결과를 나누어 확인합니다.
이론 브리핑 - Hook이란 무엇인가
CLAUDE.md는 모델이 해석하는 지침이고, Hook은 정해진 이벤트와 조건에 맞을 때 런타임이 호출하는 처리입니다. 이번 command Hook은 스크립트로 검사 결과를 정합니다. 등록만 확인하지 말고 실제 이벤트에서 스크립트가 실행됐는지도 확인하세요.
핸들러 타입은 다섯 가지입니다. command(셸 명령, 기본), http(POST 전송), mcp_tool(MCP 도구 호출), prompt(단발 LLM 판정), agent(서브에이전트 검증).
이 랩은 command만 씁니다.
이벤트는 목록이 아니라 구조입니다. 세션 안에 턴이, 턴 안에 도구 호출이 중첩됩니다. 훅을 설계할 때는 이벤트 이름을 외우는 게 아니라 어느 경계에 붙일지를 먼저 정합니다:
세션 시작·재개 사용자 요청마다 반복 ↺ 도구 호출마다 반복 ↺
SessionStart → [ UserPromptSubmit → ( PreToolUse → 도구 실행 → PostToolUse ) → Stop ] → SessionEnd
실행 전 검사·제어 성공한 도구 실행 뒤 검사·피드백
# 한 턴에 여러 도구를 호출할 수 있고, 도구를 호출하지 않는 턴도 있습니다.
이벤트 카탈로그 - 전체 훅 이벤트
| 영역 | 이벤트 | 역할 |
|---|---|---|
| LIFECYCLE 3주기 - 실행의 본류 | ||
| Session | Setup, SessionStart, SessionEnd | 세션 준비, resume, 정리 |
| Turn | UserPromptSubmit(+Expansion), Stop, StopFailure | 프롬프트와 턴 종료 |
| Tool / permission | PreToolUse, PermissionRequest/Denied, PostToolUse(+Failure), PostToolBatch | 가드와 결과 검사 |
| 주기 밖 - 독립 축 | ||
| Context / env | InstructionsLoaded, ConfigChange, CwdChanged, FileChanged, Pre/PostCompact, Worktree* | 주변 상태와 작업 공간 변화 |
| Display / 상호작용 | MessageDisplay, Notification, Elicitation(+Result) | 표시와 사용자 상호작용 |
| Agent / team | SubagentStart/Stop, TaskCreated/Completed, TeammateIdle | 오케스트레이션 관측 |
아래 명령은 이 랩의 팀 설정 전체를 재작성합니다. 별도로 추가한 설정이 있으면 먼저 확인하세요. A1의 팀 설정에 hooks 블록을 추가합니다. hooks 블록은 3단 중첩입니다. "PostToolUse"가 ① 이벤트(어느 수명주기 지점에서), "matcher": "Edit|Write"가 ② 매처(어떤 대상으로 좁혀서), 안쪽 hooks 배열이 ③ 핸들러(무엇을 실행할지)입니다. 경로는 $CLAUDE_PROJECT_DIR로 참조해 어디서 열어도 동작하게 합니다.
매처의 Write는 유효합니다. 권한 규칙에서 Write(경로)는 참조되지 않아 시작 시 경고가 뜨지만(그래서 A1에서 Edit(**)만 씁니다), 훅 매처는 A2의 권한 패턴과 다른 매칭 시스템입니다. 도구 이름의 정확 문자열 비교이고 |는 리스트 구분이며, .* 같은 문자가 섞일 때만 정규식으로 평가됩니다. 그래서 Edit|Write가 정상입니다.
| PreToolUse 등 도구 이벤트 | 도구 이름 | Bash, Edit|Write, mcp__.* |
| SessionStart | 시작 방식 | startup, resume, clear, compact, fork |
| Notification | 알림 유형 | permission_prompt, idle_prompt, … |
| ConfigChange | 구성 소스 | user_settings, project_settings, … |
| FileChanged | 리터럴 파일명 - 정규식 아님 | .envrc|.env |
| Stop, PostToolBatch 등 | matcher 미지원 | 항상 실행 |
cd ~/claude-lab/ch4
cat > .claude/settings.json << 'EOF'
{
"model": "sonnet",
"permissions": {
"allow": ["Bash(npm test *)"],
"ask": ["Edit(**)", "Bash(git push *)"],
"deny": [
"Read(.env*)", "Edit(.env*)",
"Bash(rm *)", "Bash(curl *)", "Bash(wget *)",
"WebFetch"
]
},
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/post-check.sh" }]
}]
}
}
EOF
echo "Hook 등록 완료"
post-check.sh가 없어도 등록은 성공합니다. 훅은 실행 시점에야 스크립트를 찾습니다.
이 상태로 이벤트가 발화하면 스크립트 부재는 논블로킹 오류(exit 127)로 지나가고 세션은 계속됩니다.
등록만 하고 구현을 잊으면 훅이 조용히 고장난 채 도는 이유가 이것입니다. 이제 그 실물을 만듭니다.
mkdir -p ~/claude-lab/ch4/.claude/hooks
cat > ~/claude-lab/ch4/.claude/hooks/post-check.sh << 'EOF'
#!/bin/bash
# PostToolUse: 편집된 파일 로그 + JS 구문 검사
INPUT=$(cat)
if command -v jq >/dev/null 2>&1; then
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
else
FILE=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))" 2>/dev/null)
fi
[ -z "$FILE" ] && exit 0
echo "$(date '+%H:%M:%S') edited: $FILE" >> "$CLAUDE_PROJECT_DIR/.hook.log"
case "$FILE" in
*.js|*.mjs)
if ! ERR=$(node --check "$FILE" 2>&1); then
echo "구문 오류가 감지되었습니다. 수정해 주세요: $ERR" >&2
exit 2
fi
;;
esac
exit 0
EOF
chmod +x ~/claude-lab/ch4/.claude/hooks/post-check.sh
echo "Hook 스크립트 준비 완료"
{
"hook_event_name": "PostToolUse",
"tool_name": "Edit",
"tool_input": {
"file_path": "/home/user/claude-lab/ch4/src/greet.js"
}
}
설정 JSON은 사용자가 등록하고, 입력 JSON은 이벤트가 발생할 때 런타임이 전달합니다. 스크립트는 tool_input.file_path를 읽고 JS·MJS 파일에 node --check를 실행합니다.
세션에 붙이기 전에 스크립트만 먼저 검증합니다. Hook 디버깅의 기본기입니다.
cd ~/claude-lab/ch4
echo 'function broken( {' > /tmp/broken.js
export CLAUDE_PROJECT_DIR=$PWD
# 정상 파일 → exit 0
echo '{"tool_input":{"file_path":"'$PWD'/src/greet.js"}}' | .claude/hooks/post-check.sh; echo "exit: $?"
# 구문 오류 파일 → exit 2 + stderr 메시지
echo '{"tool_input":{"file_path":"/tmp/broken.js"}}' | .claude/hooks/post-check.sh; echo "exit: $?"
unset CLAUDE_PROJECT_DIR
종료 코드와 사후 피드백
이 스크립트는 종료 코드와 stderr로 검사 결과를 전달합니다. 오류를 찾는 것은 스크립트이고, 후속 수정을 판단하는 것은 Claude입니다.
| 종료 코드 | 이번 실습에서 확인할 동작 |
|---|---|
| exit 0 | 스크립트 정상 종료. 별도 결정 출력이 없으면 정상 흐름을 계속합니다. 다른 Hook은 stdout의 구조화된 JSON으로 추가 결정을 전달할 수 있습니다. |
| exit 2 | PreToolUse에서는 실행 전 차단에 사용합니다. 이번 PostToolUse에서는 이미 실행된 도구를 되돌리지 않고 stderr를 사후 피드백으로 전달합니다. |
| exit 1·기타 | 유효한 결정 출력이 없는 일반 오류는 대부분 비차단 오류로 처리됩니다. 이벤트별 예외와 출력 계약은 공식 문서에서 확인합니다. |
구문 검사 통과는 기능 테스트 통과와 다릅니다. 사전 준비의 test.js도 PASS·FAIL 문자열을 출력하는 예제이므로, FAIL 출력과 프로세스 실패 종료 코드를 구분하세요.
A3에서 Auto 세션을 종료했으므로 ch4에서 새 세션을 엽니다. 이번에는 A3의 --settings 파일을 전달하지 않습니다.
cd ~/claude-lab/ch4
claude
/hooks
PostToolUse와 Edit|Write 등록을 확인하세요. 다음 요청 뒤 실제 사용 도구도 확인합니다. Bash로 파일을 수정했다면 이 Hook의 실행 검증으로 보지 않습니다.
src/greet.js에 goodbye(name) 함수를 추가해 주세요
cat ~/claude-lab/ch4/.hook.log
실제 Edit·Write 호출과 그 편집 시각·파일 경로에 해당하는 새 로그를 함께 확인합니다. 구문 오류 피드백이 전달됐다면 후속 수정과 재검사 결과도 확인하세요.
/hooks는 등록 상태, 단독 테스트는 스크립트 동작, 실제 편집의 새 로그는 이벤트에서 로그 코드까지 실행됐음을 보여줍니다.
단독 테스트도 같은 로그 파일을 사용하고 로그는 검사 전에 기록됩니다. 로그 파일의 존재만으로 검사 통과나 수정 완료를 판단하지 마세요.
PostToolUse는 이미 저장된 파일을 자동으로 되돌리지 않습니다.
prettier --write나 eslint --fix를 넣는 것이 auto-format 패턴입니다.
다음 예제는 모델 전환 전에 사용자에게 안내를 표시합니다. systemMessage JSON을 stdout으로 반환하며, 모델 전환을 차단하는 결정은 포함하지 않습니다.
아래는 추가할 JSON 구조입니다. 실제 적용은 이어지는 Terminal 명령을 다른 터미널에서 실행하세요. 기존 설정과 PostToolUse Hook을 유지하며, 이전 stderr 안내 예제가 있으면 교체합니다.
{
"hooks": {
"PreModelSwitch": [
{
"hooks": [
{
"type": "command",
"command": "echo '{\"systemMessage\":\"모델 전환 감지, 전환 사유를 확인하세요\"}'"
}
]
}
]
}
}
설정 병합 명령에는 Python 3가 필요합니다. python3 --version으로 확인하세요. 실행 대상은 위에서 만든 ch4 설정 파일입니다.
Terminal 적용 명령 열기 · 기존 설정 보존
python3 - "${CH4_SETTINGS_FILE:-$HOME/claude-lab/ch4/.claude/settings.json}" <<'PY'
import json
import stat
import sys
import tempfile
from pathlib import Path
path = Path(sys.argv[1]).expanduser()
settings = json.loads(path.read_text(encoding="utf-8"))
old = "echo '모델 전환 감지, 전환 사유를 확인하세요' >&2"
new = "echo '{\"systemMessage\":\"모델 전환 감지, 전환 사유를 확인하세요\"}'"
groups = settings.setdefault("hooks", {}).setdefault("PreModelSwitch", [])
found = False
for group in groups:
for handler in group.get("hooks", []):
if handler.get("type") == "command":
if handler.get("command") == old:
handler["command"] = new
if handler.get("command") == new:
found = True
if not found:
groups.append({"hooks": [{"type": "command", "command": new}]})
temporary = None
try:
with tempfile.NamedTemporaryFile(
mode="w", encoding="utf-8", dir=path.parent,
prefix=".settings-", suffix=".tmp", delete=False
) as output:
temporary = Path(output.name)
output.write(json.dumps(settings, ensure_ascii=False, indent=2) + "\n")
temporary.chmod(stat.S_IMODE(path.stat().st_mode))
temporary.replace(path)
finally:
if temporary is not None and temporary.exists():
temporary.unlink()
print("PreModelSwitch 안내 Hook 반영 완료:", path)
PY
명령 실행 뒤 현재 세션의 /hooks에서 PreModelSwitch 등록을 확인하고 /model로 모델을 바꿔 안내가 표시되는지 확인하세요. 등록이 보이지 않으면 ch4에서 세션을 다시 시작합니다.
공식 문서: Hook 입력·출력과 matcher
MCP 서버 연결, 첫 외부 도구
10분인증이 필요 없는 Claude Code 공식 문서 MCP 서버를 연결해 add → 상태 확인 → 사용 → 스코프 이해 → 제거의 전체 수명주기를 경험합니다.
A4의 Claude 세션을 종료하고 터미널에서 서버를 등록합니다.
/exit
cd ~/claude-lab/ch4
claude mcp add --transport http --scope local claude-code-docs https://code.claude.com/docs/mcp
claude mcp list | grep claude-code-docs
claude-code-docs: https://code.claude.com/docs/mcp (HTTP) - ✔ Connected
Connected는 서버 연결 상태입니다. 실제 도구 호출과 응답 성공은 다음 단계에서 확인합니다.
새 세션을 열고 서버 도구를 호출합니다. 컨텍스트 변화도 관찰하려면 검색 전에 아래 선택 항목을 열어 기준 화면을 확인하세요.
cd ~/claude-lab/ch4
claude
선택 관찰 · 검색 전 컨텍스트
Tool Search는 필요한 도구 정의를 지연 로드합니다. 적용 여부와 표시는 모델·설정·버전에 따라 다를 수 있습니다.
/context all
MCP tools, /mcp (loaded on-demand)
Available
├ mcp__claude-code-docs__query_docs_filesystem_claude_code_docs
├ mcp__claude-code-docs__search_claude_code_docs
├ mcp__claude-code-docs__submit_feedback
사용 가능 목록과 로드된 상세 정의를 구분하세요. 실제 도구 이름·수와 표시 형식은 서버의 현재 상태를 따릅니다.
claude-code-docs 서버를 사용해서 MCP_TIMEOUT 환경변수가 무엇을 하는지 찾아 주세요
권한 규칙과 현재 모드에 따라 도구 사용 승인을 요청할 수 있습니다. 서버 이름이 붙은 실제 도구 호출, 반환 결과, 그 결과에 근거한 답변을 확인하세요. 연결만 됐거나 호출이 실패한 상태는 검색 완료로 보지 않습니다.
선택 관찰 · 도구 정의의 지연 로드 전후 비교
/context all
검색 전에 확인했다면 새로 로드된 도구 정의가 있는지 비교합니다. 여러 정의가 로드되거나 이미 로드돼 차이가 없을 수도 있습니다. 토큰 증가량이나 도구 수가 예시와 같을 필요는 없습니다.
Tool Search가 도구 정의를 지연 로드해도 서버 안내·도구 목록과 실제 반환 결과의 비용까지 없어지는 것은 아닙니다. 전체 컨텍스트 차이를 지연 로드된 정의의 분량으로만 해석하지 마세요.
지금 서버는 local 스코프로 등록돼 나만, 이 프로젝트에서 사용합니다. 팀과 공유하려면 project 스코프로 등록합니다.
| 스코프 | 저장 위치 | 대상 |
|---|---|---|
| local (기본) | ~/.claude.json의 프로젝트 항목 | 나만, 이 프로젝트만 |
| project | 프로젝트 루트 .mcp.json | 저장소를 클론한 팀원. 사용 승인 상태는 별도 |
| user | ~/.claude.json 전역 | 나만, 모든 프로젝트 |
MCP의 local은 .claude/settings.local.json에 저장되지 않습니다. 같은 이름의 서버는 local > project > user 순으로 한 정의를 선택하며 URL·인자를 필드별로 합치지 않습니다.
/exit
cd ~/claude-lab/ch4 &&
claude mcp remove --scope local claude-code-docs &&
claude mcp add --transport http --scope project claude-code-docs https://code.claude.com/docs/mcp &&
cat .mcp.json
git status
.mcp.json이 프로젝트 루트에 생성됩니다. 이 파일을 커밋하면 서버 정의를 팀과 공유합니다. local을 먼저 제거한 이유는 같은 이름의 local 정의가 project보다 우선하기 때문입니다. stdio 서버는 claude mcp add 이름 -- npx -y 패키지명 형식으로 등록합니다.
서버를 등록한 사람의 승인 상태가 팀원에게 전달되지는 않습니다. 같은 프로젝트에서 MCP 서버의 승인·거절 기록을 초기화하고 새 세션을 엽니다. 아래 실습은 프로젝트 서버의 자동 승인 설정이 없는 경우를 기준으로 합니다.
cd ~/claude-lab/ch4 &&
claude mcp reset-project-choices &&
claude
New MCP server found in this project: claude-code-docs
Use this MCP server
Use this and all future MCP servers in this project
❯ Continue without using this MCP server
Enter to confirm · Esc to cancel
예시에서는 세 번째 항목이 선택돼 있습니다. 방향키로 Use this MCP server를 선택해 현재 문서 서버만 승인하세요. 화면 문구는 버전에 따라 다를 수 있습니다. 승인 화면이 나오지 않으면 자동 승인 설정과 조직 정책을 확인합니다.
/mcp
claude-code-docs의 승인·연결 상태를 확인하세요. 프로젝트 서버 사용 승인, 외부 서비스 인증, 개별 도구 사용 권한은 서로 다릅니다. 이번 문서 서버는 별도 로그인이 필요 없지만 도구 호출 권한 판단은 별도로 적용됩니다.
사용하지 않을 실습 서버를 제거합니다. Claude 세션을 종료한 뒤 터미널에서 실행하세요.
/exit
cd ~/claude-lab/ch4
claude mcp remove --scope project claude-code-docs
불필요한 연결을 정리하면 연결 관리 비용과 컨텍스트 사용을 줄이는 데 도움이 됩니다.
공식 문서: MCP 연결·스코프·Tool Search
슈퍼랩, Team Starter Kit을 빌드하라
지금부터는 따라하기가 아니라 빌드 미션입니다. Part A에서 익힌 메커니즘으로 팀에 바로 커밋할 수 있는 에셋 3종(커스텀 커맨드, 스킬, 스타터 킷 문서)을 만듭니다. 각 미션은 요구사항과 완성 기준(Definition of Done)만 제시합니다. 구현 경로는 자유입니다.
배경 지식 한 장: 커스텀 커맨드는 스킬로 통합되었습니다.
.claude/skills/이름/SKILL.md를 만들면 /이름 명령이 생기고
(구 .claude/commands/이름.md도 계속 동작), 스킬 디렉토리는 파일 감시로 즉시 반영됩니다.
단, 프로젝트에 skills 디렉토리를 처음 만드는 경우라면 세션을 재시작해야 감시가 시작됩니다.
acceptEdits로 전환하고 시작
1분슈퍼랩은 Claude가 파일을 여러 개 만들고 고치므로, 기본 모드로는 승인 요청이 쏟아져 흐름이 끊깁니다.
파일 편집을 자동 수락하는 acceptEdits를 팀 설정에 고정하고, 학습용 ask 규칙과 Hook은 잠시 비워 둡니다.
다만 deny 3줄은 그대로 남깁니다 - 속도를 얻더라도 최소한의 경계는 지키기 위해서입니다.
cd ~/claude-lab/ch4
cat > .claude/settings.json << 'EOF'
{
"permissions": {
"defaultMode": "acceptEdits",
"deny": ["Read(.env*)", "Edit(.env*)", "Bash(rm *)"]
}
}
EOF
echo '{}' > .claude/settings.local.json
/exit
claude
defaultMode는 permissions 블록이라 핫 리로드 대상입니다. 위 재시작은 설정 반영이 아니라 컨텍스트를 비우기 위한 것입니다.
/standup, 매일 쓰는 커맨드 스킬
10분
어제 커밋을 동적 컨텍스트 주입으로 읽어 스탠드업 초안을 만들어 주는
/standup 커맨드를 만듭니다.
Slack에 붙여넣기 좋은 형식이 목표입니다.
.claude/skills/standup/SKILL.md 를 만들어 주세요. 요구사항:
1) frontmatter: description은 "일일 스탠드업 초안 생성", disable-model-invocation: true, argument-hint: "[오늘 할 일]"
2) 본문 맨 위에서 동적 컨텍스트 주입 문법 !`git log --oneline --since="1 day ago"` 로 어제 커밋을 주입
3) $ARGUMENTS 가 있으면 오늘 할 일로 정리하고, 없으면 커밋 흐름에서 추천
4) 출력 형식: ### 어제 / ### 오늘 / ### 블로커 3개 섹션의 마크다운, Slack에 붙여넣기 좋게
생성된 파일을 cat .claude/skills/standup/SKILL.md로 확인하고,
frontmatter와 !`...` 주입 라인이 요구사항대로인지 검토하세요. 이 프로젝트에
skills 디렉토리가 처음 생긴 것이므로 세션을 한 번 재시작(/exit 후 claude)합니다.
/standup "PR 리뷰 2건, 워크샵 랩 마무리"
### 어제
- feat: add greeting flair
- chore: settings lab scaffold
### 오늘
- PR 리뷰 2건
- 워크샵 랩 마무리
### 블로커
- 없음
막힐 때 열어보기, 완성본 SKILL.md
---
description: 일일 스탠드업 초안 생성. 어제 커밋과 오늘 할 일을 정리한다.
disable-model-invocation: true
argument-hint: "[오늘 할 일]"
---
## 어제의 커밋
!`git log --oneline --since="1 day ago"`
## 지시
위 커밋 목록을 사람이 읽기 쉬운 한 줄 요약으로 정리해 "### 어제" 섹션을 만드세요.
$ARGUMENTS 가 있으면 "### 오늘" 섹션의 항목으로 정리하고, 없으면 커밋 흐름을 보고 오늘 할 일을 추천하세요.
마지막에 "### 블로커" 섹션을 넣되 언급된 것이 없으면 "- 없음"으로 채우세요.
전체를 Slack에 붙여넣기 좋은 마크다운으로만 출력하세요.
release-notes, 격리 실행 스킬
12분
커밋 범위를 인자로 받아 릴리스 노트를 생성하는 스킬을 만듭니다.
이번에는
context: fork로 읽기 전용 Explore 에이전트에서 격리 실행시켜,
메인 컨텍스트를 지키면서 결과만 받아옵니다.
Chapter 2의 서브에이전트와 이 챕터의 스킬이 만나는 지점입니다.
.claude/skills/release-notes/SKILL.md 를 만들어 주세요. 요구사항:
1) frontmatter: description "커밋 범위로 릴리스 노트 생성", disable-model-invocation: true,
argument-hint: "[커밋 범위]", context: fork, agent: Explore
2) 본문에서 !`git log --oneline --no-merges $ARGUMENTS` 로 해당 범위 커밋을 주입
3) 커밋을 Added / Changed / Fixed 로 분류한 마크다운 릴리스 노트를 작성하도록 지시
4) 대상 독자는 개발자가 아닌 사용자라는 점을 명시
/release-notes HEAD~2..HEAD
HEAD~2가 가리킬 커밋이 없어
git log가 fatal: ambiguous argument 류의 에러를 냅니다. 정상적인 상황이니
범위를 /release-notes HEAD~1..HEAD로 줄이거나, 커밋을 하나 더 만든 뒤 다시 실행하세요.
인자 검증이 없는 스킬의 전형적인 실패 모드를 먼저 만나 본 셈입니다.
[Explore 에이전트로 포크 실행] # 프롬프트 아래 서브에이전트 패널에 표시
## Release Notes
### Added
- 인사 기능에 새로운 표현 추가
### Changed / Fixed
- ...
# 결과만 메인으로 반환, 탐색 과정은 격리 컨텍스트에서 소비
실행 중 프롬프트 아래 서브에이전트 패널과 /tasks에서 포크 실행을 관찰하세요.
스킬 파일 저장은 즉시 반영되므로(skills 디렉토리는 M1에서 이미 감시 시작) 재시작이 필요 없습니다.
막힐 때 열어보기, 완성본 SKILL.md
---
description: 지정한 커밋 범위의 변경 사항으로 릴리스 노트를 생성한다.
disable-model-invocation: true
argument-hint: "[커밋 범위]"
context: fork
agent: Explore
---
## 대상 커밋
!`git log --oneline --no-merges $ARGUMENTS`
## 지시
위 커밋들을 분석해 릴리스 노트를 작성하세요.
- 형식: "## Release Notes" 아래 "### Added", "### Changed", "### Fixed" 세 분류
- 각 항목은 커밋 메시지를 그대로 옮기지 말고, 개발자가 아닌 사용자가 이해할 문장으로 다시 쓰세요
- 해당 분류에 항목이 없으면 그 섹션은 생략하세요
agent 필드로 고르며, 생략하면 general-purpose입니다.
자유 빌드, 본인 팀의 반복 작업 하나
10분
이번엔 요구사항도 직접 정합니다.
본인 팀에서 매주 반복하는 작업 하나를 골라
스킬로 만드세요.
좋은 후보는 "매번 비슷한 지시문을 복사해 붙여넣는 일"입니다.
| 아이디어 | 핵심 재료 |
|---|---|
| /pr-desc, PR 설명 초안 | !`git diff main...HEAD --stat` 주입 + 팀 PR 템플릿 |
| /commit-msg, 커밋 메시지 컨벤션 | !`git diff --cached` 주입 + Conventional Commits 규칙 |
| /review-checklist, 리뷰 체크리스트 | 팀 체크리스트를 본문에 내장 (참조형, 플래그 없이) |
| /test-plan, 테스트 시나리오 초안 | $ARGUMENTS로 대상 기능, 정상/엣지/예외 3분류 지시 |
| /translate-ko, 기술 문서 한국어화 | $ARGUMENTS 파일 경로 + 용어집을 supporting file로 |
.claude/skills/[스킬이름]/SKILL.md 를 만들어 주세요.
- 목적: [이 스킬이 해결하는 반복 작업]
- 트리거: [내가 직접 /명령으로만 vs Claude가 관련 상황에서 자동으로]
- 입력: [$ARGUMENTS로 받을 것 / !`...`로 주입할 실시간 데이터]
- 출력 형식: [정확한 섹션 구조나 예시]
만든 뒤 이 스킬을 스스로 한 번 호출해서 결과가 형식에 맞는지 검증까지 해 주세요
검증은 "What skills are available?"로 목록 노출을 확인하고, 직접 호출해 출력 형식을 봅니다. trigger 설계가 애매하면 M1의 판단 기준(내가 타이밍을 정하는 액션 → disable-model-invocation)을 다시 적용하세요.
아래 세 줄을 한 줄씩 실행하세요. 각 명령의 응답을 확인하고 다음으로 넘어갑니다.
/plugin marketplace add anthropics/claude-plugins-official
공식 마켓은 보통 자동 등록되어 있어 "이미 등록됨" 메시지가 나올 수 있습니다. 정상입니다.
/plugin install skill-creator@claude-plugins-official
/reload-plugins
skill-creator로 방금 만든 스킬을 평가해 주세요. 테스트 케이스 3개를 만들어 스킬이 있을 때와 없을 때를 비교해 주세요
skill-creator는 테스트 케이스 작성, 격리 실행, 채점, 스킬 유무 벤치마크까지 자동화하는 공식 플러그인입니다. 시간이 남는 분만 진행하세요.
패키징과 공유, 킷을 팀의 것으로
5분
80분의 산출물(설정, Hook, 스킬 3종)을 온보딩 가능한 스타터 킷으로 패키징합니다.
문서화도 Claude에게 시킵니다.
.claude 디렉토리 전체(settings.json, hooks, skills)를 훑고, 새 팀원이 5분 안에 이해할 온보딩 README.md를 프로젝트 루트에 만들어 주세요. 각 스킬의 사용 예시 한 줄씩 포함해 주세요
cd ~/claude-lab/ch4
echo ".claude/settings.local.json" >> .gitignore
git add -A && git commit -m "feat: team starter kit (settings, hooks, skills)"
git log --oneline | head -3
| 경로 | 방법 | 적합한 경우 |
|---|---|---|
| 프로젝트 커밋 | .claude/를 저장소에 커밋 (지금 한 것) | 한 저장소에서 함께 일하는 팀 |
| 플러그인 | skills + hooks + agents를 플러그인으로 묶어 마켓플레이스 배포 | 여러 저장소, 여러 팀에 배포 |
| Managed | 관리 설정 경로에 배포 (Chapter 3) | 조직 표준으로 강제 |
.claude-plugin/plugin.json을 추가하면 그 폴더가 플러그인으로 로드되어
agents, hooks, MCP 서버까지 함께 묶을 수 있습니다. 팀 데모 시간에 오늘 만든 스킬 하나를 시연해 보세요.
마무리, 학습 목표 체크리스트
Chapter 4의 학습 목표를 스스로 점검하세요. 미달성 항목은 강의 자료의 해당 Part를 다시 확인하거나 Slack 채널에 질문을 남기세요.
| 목표 | 확인 질문 | 관련 |
|---|---|---|
| Layers | 5단 우선순위와 팀/개인 설정 분리를 체험했는가 | A1 |
| Permissions | allow/ask/deny 3단 대조와 와일드카드 매칭 규칙을 이해했는가 | A2 |
| Modes | Auto mode 규칙 등록과 도구별 실행 결과를 확인하고, 도구 차단과 최종 파일 결과를 구분했는가 | A3 |
| Hooks | PostToolUse 피드백 루프(exit 2)를 단독+세션에서 검증했는가 | A4 |
| MCP | 서버 수명주기(add/list/사용/remove)와 3스코프를 이해했는가 | A5 |
| Command Skill | 동적 주입 + $ARGUMENTS로 커맨드 스킬을 빌드했는가 | M1 |
| Fork Skill | context: fork로 격리 실행 스킬을 빌드했는가 | M2 |
| Own Asset | 본인 팀의 반복 작업 하나를 스킬로 만들었는가 | M3 |
| Share | 스타터 킷을 커밋하고 공유 경로 3가지를 아는가 | M4 |
가져가면 좋은 추천 스킬 리스트
팀에 돌아가 처음 만들 스킬 후보들입니다. 전부 오늘 배운 패턴(인자, 주입, 격리, 수동 전용)으로 만들 수 있습니다.
| 스킬 | 하는 일 | 만들 때 포인트 |
|---|---|---|
/release-notes | 커밋 범위를 사용자용 릴리스 노트로 | 오늘 빌드, context: fork로 격리 실행 |
/standup | 어제 커밋과 오늘 계획을 데일리 포맷으로 | 오늘 빌드, git log 주입 |
/pr-desc | 현재 diff를 읽어 PR 제목과 본문 초안 | !`git diff --stat` 주입 + 템플릿 강제 |
/test-gap | 변경 파일 대비 빠진 테스트 목록화 | 읽기 전용 도구만, 제안까지만 하게 제한 |
/db-migrate | 마이그레이션 절차를 단계별 안내 | disable-model-invocation: true, 사람이 부를 때만 |
/incident-brief | 로그 구간을 타임라인 요약으로 | argument-hint로 시간 범위, 결론 3줄 강제 |
READING CHECK
이 페이지를 읽으셨나요?
읽음 표시는 실습 체크포인트와 별도로 관리됩니다. 아직 읽지 않음