사전 준비 확인
2분80분 내내 사용할 실습 프로젝트를 만듭니다. Hook 스크립트가 jq를 사용하므로 설치 여부도 확인합니다.
jq --version || echo "jq 없음: brew install jq 권장, 없어도 Hook은 python3로 대체 동작"
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 # ★ 개인 - 커밋 제외, 자동 gitignore (이 랩 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
/status
Setting sources: User settings, Shared project settings,
Project local settings, Enterprise managed settings (remote)
# Shared project settings = .claude/settings.json
# Project local settings = .claude/settings.local.json
# User settings = ~/.claude/settings.json
# Enterprise managed settings = 조직 정책, 원격 전달
# 한 줄로 나오며 파일 경로는 표시되지 않습니다
이 파일에는 Claude도 씁니다. read-only 내장 집합에 없는 Bash 명령을 승인해 확인합니다.
npm ls로 의존성 목록을 보여주세요
프롬프트에서 "다시 묻지 않기" 계열 옵션을 선택하세요.
cat ~/claude-lab/ch4/.claude/settings.local.json
"allow": ["Bash(npm run *)", "Bash(npm ls *)"]
# 손으로 쓴 규칙 옆에 자동 생성된 규칙이 추가됨
# 대화상자는 항상 공백 + * 형식으로 씁니다
# 팀 파일 settings.json은 건드려지지 않았습니다
/permissions
allow 목록에 팀 규칙(npm test)과 개인 규칙(npm run), 그리고 방금 자동 생성된 규칙(npm ls)이 병합되어 함께 보이면 성공입니다. 권한 규칙은 스코프 간 덮어쓰기가 아니라 병합되며, deny는 어느 스코프에 있든 우선합니다.
.claude/settings.json은 커밋해 팀 표준으로, settings.local.json은
.gitignore에 넣어 개인용으로 유지하세요. 설정 파일은 저장 즉시 핫 리로드되어 permissions와 hooks는 재시작 없이 적용됩니다. 반면 위 블록의 model은 세션 시작 시 한 번만 읽습니다(변경은 /model).
설정 가능한 전체 키는 세션에서 /config --help로 확인할 수 있습니다.
권한 규칙, 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 .를 허용합니다. 러너를 허용할 때는 내부 명령까지 포함한 규칙을 쓰세요.복합 명령:
&&, ||, ;, |, &, 개행으로 나뉜 각 서브명령이 독립적으로 매칭되어야 합니다.
그래서 인자를 제약하려는 패턴은 근본적으로 취약합니다. rm을 좁게 막는 대신 전면 차단하고 예외를 훅으로 여는 이유입니다.
npm test를 실행해 주세요
승인 프롬프트 없이 바로 실행되고 PASS가 출력됩니다. allow 규칙 Bash(npm test *)에 매칭되었기 때문입니다.
curl https://example.com 을 실행해서 응답을 보여주세요
Ran 1 shell command
→ curl 실행 권한이 거부되어 요청을 보내지 못했습니다.
# 승인 기회 자체가 없습니다. .env 읽기 요청도 동일하게 차단됩니다
# 거부 사실이 Claude에게 전달되므로, Claude가 대안을 제안할 수 있습니다
# 제안 내용은 세션마다 다릅니다. 따라가지 말고 다음 단계로 넘어가세요
Bash(curl *)): 도구는 컨텍스트에 남고, Claude가 호출을 시도한 뒤 차단됩니다.
거부 사실이 Claude에게 전달되므로 위처럼 우회를 모색합니다.괄호 없는 도구 이름(
WebFetch): 도구가 컨텍스트에서 제거되어 Claude는 존재 자체를 모릅니다. 시도도, 거부 메시지도 없습니다.네트워크를 정말 막으려면
curl/wget deny와 WebFetch 통제를 함께 걸어야 합니다.
한쪽만 막으면 다른 쪽으로 나갑니다.
src/greet.js의 인사말을 "Hi"로 바꿔 주세요
Edit은 ask 규칙이므로 diff 미리보기와 승인 프롬프트가 표시됩니다. 승인해서 진행하세요. A1의 Bash 승인과 달리 파일 수정 승인은 설정 파일에 기록되지 않고 세션 종료와 함께 사라집니다. 영구 기록되는 것은 Bash 명령 승인뿐입니다.
permission mode, 프롬프트의 기본값을 정하는 층
5분A2에서 규칙이 예외를 정하는 것을 봤습니다. 규칙에 걸리지 않은 호출의 기본 동작을 정하는 것이 permission mode입니다. A2의 설정을 그대로 둔 채 모드만 올려서, 무엇이 바뀌고 무엇이 안 바뀌는지 대조합니다.
| 모드 | 규칙에 매칭되지 않은 호출 | 쓰는 자리 |
|---|---|---|
default | 도구 첫 사용마다 프롬프트 | 기본값. CLI에는 Manual로 표시 |
acceptEdits | 파일 편집과 mkdir/touch/rm/rmdir/mv/cp/sed 자동 수락 | 작업 디렉토리 안에서 편집이 많을 때 |
plan | 읽기와 read-only 셸만, 소스 편집 안 함 | 낯선 코드베이스 파악. /plan |
auto |
백그라운드 안전성 검사와 함께 자동 승인 | 요청과 행동의 정합성을 분류기가 판단 |
dontAsk | 자동 거부 (프롬프트 없음) | "allow에 있는 것만 실행"의 정답. CI |
bypassPermissions | 프롬프트 생략 | 컨테이너/VM 등 격리 환경 전용 |
{
"permissions": { "defaultMode": "auto" }, # user, 관리 설정에서만 유효 - 프로젝트/local의 "auto"는 무시
"autoMode": { # 분류기 규칙 - glob이 아니라 문장으로 작성
"environment": ["$defaults", "Sensitive remote targets: prod-*"], # 신뢰 + 민감 대상 선언
"allow": ["$defaults", "kubectl get 계열은 안전"], # 예외 허용
"soft_deny": ["$defaults", "terraform apply 금지"], # 명시 요청 시에만 허용
"hard_deny": ["$defaults", "IAM 정책 변경"], # 무조건 차단 - allow로도 못 뚫음
"classifyAllShell": true
}
}
세션 중 Shift+Tab으로 모드를 순환 전환합니다. 입력창 아래 표시가 accept edits로 바뀔 때까지 누르세요.
세션 한정이라 설정 파일은 건드리지 않습니다. 영구 적용은 permissions.defaultMode로 하며, 그건 슈퍼랩 PREP에서 씁니다.
npm test를 실행한 다음, curl https://example.com 도 실행해 주세요
npm test → 무확인 실행 # allow 규칙, 모드와 무관
curl → 차단 # deny 규칙은 모든 모드에서 유효
# 모드를 올려도 거부 메시지와 대안 제안까지 그대로입니다
src/greet.js에 farewell(name) 함수를 추가해 주세요
acceptEdits인데도 승인 프롬프트가 그대로 뜹니다.
ask: Edit(**)가 명시적으로 매칭되므로 모드의 기본 동작까지 내려가지 않기 때문입니다.
모드는 규칙에 걸리지 않은 호출만 처리합니다.
deny 규칙은 모든 모드에서 적용되고, 명시적 ask 규칙은 bypassPermissions에서도 프롬프트를 띄웁니다.
/나 홈 디렉토리를 대상으로 하는 rm은 회로 차단기로 남아 있습니다.
PreToolUse 훅이 allow를 반환해도 deny/ask 규칙을 넘지 못합니다(Part 3).
즉 모드는 기본값을, 규칙은 예외를 정하며, 규칙이 모드를 이깁니다.
permissions.disableBypassPermissionsMode와 permissions.disableAutoMode를 "disable"로 두면
해당 모드가 Shift+Tab 순환에서 사라지고 --permission-mode도 거부됩니다. 관리 설정(Part 3)에 두면 사용자가 되돌릴 수 없습니다.
auto 모드의 판단 기준을 바꾸는 autoMode 키는 user / --settings / 관리 설정에서만 읽히고 프로젝트와 local 설정에서는 무시됩니다.
클론한 저장소가 분류기 기준을 손댈 수 없게 한 설계입니다.
PostToolUse Hook, 편집 뒤 자동 검사
10분Claude가 파일을 수정할 때마다 자동으로 구문 검사가 도는 Hook을 답니다. Ch3의 PreToolUse가 실행 전 차단이라면, PostToolUse는 실행 후 피드백입니다. exit 2의 stderr가 Claude에게 전달되어 스스로 고치는 루프가 만들어지는 것이 핵심입니다.
이론 브리핑 - Hook이란 무엇인가
CLAUDE.md에 "파일을 수정하면 반드시 검사를 돌려라"라고 적어도, 따를지는 모델의 선택입니다. 지시는 확률적이라 실행이 생략될 수 있고, 컴팩션과 세션 리셋에서 소실됩니다. Hook은 그 반대편에 있습니다. 모델에게 부탁하는 지시가 아니라 Claude Code 런타임이 수명주기 이벤트마다 예외 없이 실행하는 핸들러입니다. "반드시"가 필요한 규칙은 지시가 아니라 훅에 둡니다.
핸들러 타입은 다섯 가지입니다. command(셸 명령, 기본), http(POST 전송), mcp_tool(MCP 도구 호출), prompt(단발 LLM 판정), agent(서브에이전트 검증).
이 랩은 command만 씁니다.
이벤트는 목록이 아니라 구조입니다. 세션 안에 턴이, 턴 안에 도구 호출이 중첩됩니다. 훅을 설계할 때는 이벤트 이름을 외우는 게 아니라 어느 경계에 붙일지를 먼저 정합니다:
세션당 1회 턴마다 반복 ↺ 도구 호출마다 반복 ↺
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 스크립트 준비 완료"
{
"session_id": "abc123", # ── 공통 필드: 모든 이벤트가 받음
"transcript_path": "~/.claude/projects/…/….jsonl",
"cwd": "/home/dev/claude-lab/ch4",
"permission_mode": "acceptEdits",
"hook_event_name": "PostToolUse",
"tool_name": "Edit", # ── 도구 이벤트 추가 필드
"tool_input": { # 도구마다 다름:
"file_path": "src/greet.js", # Edit → file_path, new_string
"new_string": "…" # Write → file_path, content
}, # Bash → command
"tool_response": { "…": "…" }, # PostToolUse 한정 - 도구 실행 결과
"tool_use_id": "toolu_01…"
}
세션에 붙이기 전에 스크립트만 먼저 검증합니다. 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
이론 브리핑 - 종료 코드, 훅의 결정 계약
방금 단독 검증에서 exit 0과 exit 2를 눈으로 확인했습니다. 종료 코드는 훅이 런타임에 내리는 결정입니다:
| 종료 코드 | 의미 | Claude Code의 처리 |
|---|---|---|
| exit 0 | 결정 없음 (기권) | 정상 흐름으로 위임 - 침묵은 승인이 아니라 기권입니다. stdout은 JSON 결정으로 파싱을 시도하며, 대부분의 이벤트에서 Claude에게 보이지 않습니다 (UserPromptSubmit, SessionStart만 컨텍스트로 주입) |
| exit 2 | 차단 신호 | stderr가 Claude에게 전달됩니다. PreToolUse면 호출 자체를 차단하고, PostToolUse는 도구가 이미 실행된 뒤라 차단 대신 사후 피드백이 됩니다. 이 랩의 자가 수정 루프가 정확히 이 경로입니다 |
| exit 1, 기타 | 고장 (논블로킹) | 오류로만 표시하고 실행은 계속됩니다 - 차단이 아닙니다 |
permissionDecision 등)은
exit 0에서만 파싱되므로 exit 2와 혼용하지 마세요. 세밀한 결정 형식은 Ch3 자료를 참조하세요.
src/greet.js에 goodbye(name) 함수를 추가해 주세요
cat ~/claude-lab/ch4/.hook.log
편집 시각과 파일 경로가 기록되어 있으면 Hook이 동작한 것입니다. 세션에서 /hooks를 입력하면
등록된 Hook 구성을 확인할 수 있습니다. 만약 Claude가 구문 오류가 있는 코드를 저장하면
stderr 메시지가 Claude에게 전달되어 다음 턴에서 스스로 수정합니다.
prettier --write나 eslint --fix를 넣는 것이 auto-format 패턴입니다.
MCP 서버 연결, 첫 외부 도구
10분인증이 필요 없는 Claude Code 공식 문서 MCP 서버를 연결해 add → 상태 확인 → 사용 → 스코프 이해 → 제거의 전체 수명주기를 경험합니다.
cd ~/claude-lab/ch4
claude mcp add --transport http 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
서버를 연결하면 도구 정의가 전부 컨텍스트로 들어올 것 같지만, 그렇지 않습니다. tool search(기본 활성)가 세션 시작 시점에는 도구 이름과 server instructions만 올려 두고, 도구의 상세 정의는 Claude가 실제로 그 도구를 쓰려고 할 때 가져옵니다. 그래서 서버를 여러 개 연결해도 컨텍스트가 급격히 줄지 않습니다.
먼저 아무것도 쓰지 않은 상태의 컨텍스트를 봅니다. all을 붙이면 항목별 상세가 펼쳐집니다.
/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
loaded on-demand가 곧 지금 상태입니다. Available 목록에 이름은 다 보이지만, 각 도구의 정의는 아직 컨텍스트에 없습니다.
claude-code-docs 서버를 사용해서 MCP_TIMEOUT 환경변수가 무엇을 하는지 찾아 주세요
첫 호출에서 새 도구 사용 승인을 요청합니다. 승인하면 출력의 도구 호출에 서버 이름 라벨이 붙어, 답이 웹 검색이 아닌 MCP 서버에서 왔음을 확인할 수 있습니다.
/context all
같은 항목을 다시 보세요. 방금 호출한 search 도구만 정의가 실려 토큰이 늘어났고, 나머지 두 도구는 여전히 이름만 있습니다.
all이 필요한 이유입니다.
/mcp로 서버 패널도 열어 보세요.
방금 등록은 기본값인 local 스코프였습니다. 나만, 이 프로젝트만 쓸 수 있습니다. 팀원도 같은 서버를 쓰게 하려면 project 스코프로 올립니다.
| 스코프 | 저장 위치 | 대상 |
|---|---|---|
| local (기본) | ~/.claude.json의 프로젝트 항목 | 나만, 이 프로젝트만 |
| project | 프로젝트 루트 .mcp.json | 저장소를 클론한 팀 전체 (승인 프롬프트) |
| user | ~/.claude.json 전역 | 나만, 모든 프로젝트 |
claude mcp remove claude-code-docs
claude mcp add --transport http --scope project claude-code-docs https://code.claude.com/docs/mcp
cat .mcp.json
git status
claude mcp list | grep claude-code-docs
참고 형식이던 그 JSON이 실제로 생성됩니다. 위치는 .claude 안이 아니라
프로젝트 루트이고, git status에 untracked로 뜹니다 - 커밋하면 팀 전체에 배포됩니다.
local을 먼저 지운 이유: 같은 이름이 여러 스코프에 있으면 local > project > user 순으로 항목이
통째로 이기고 필드 병합은 없습니다. local 등록이 남아 있으면 팀 설정을 고쳐도 내 화면은 안 바뀝니다.
stdio 서버(로컬 프로세스)는 claude mcp add 이름 -- npx -y 패키지명 형식으로 등록합니다.
.mcp.json은 클론과 함께 도착하는 파일이고 임의 명령을 실행하는 서버를 실어올 수 있습니다.
그래서 저장소는 자기 자신을 승인할 수 없고, 처음 쓰는 사람에게 승인 프롬프트가 뜹니다.
A3의 모드 잠금(프로젝트 설정의 auto 무시)과 같은 원리입니다. 그 팀원의 화면을 지금 내 머신에서 재현합니다.
claude mcp reset-project-choices
claude mcp list
claude-code-docs: https://code.claude.com/docs/mcp (HTTP) - ⏸ Pending approval (run `claude` to approve)
이제 claude로 새 세션을 열면 승인 프롬프트가 뜹니다.
저장소를 클론한 팀원이 보는 바로 그 화면입니다. 승인하면 연결되고, 거절하면 서버는 비활성 상태로 남습니다.
프롬프트를 실수로 닫았다면 /mcp 패널에서 승인할 수 있습니다.
2번에서 본 대로, 연결된 서버는 쓰지 않아도 이름값을 냅니다. 쓰지 않는 서버는 제거하세요.
claude mcp remove --scope project claude-code-docs
슈퍼랩, Team Starter Kit을 빌드하라
지금부터는 따라하기가 아니라 빌드 미션입니다. Part A에서 익힌 메커니즘으로 팀에 바로 커밋할 수 있는 에셋 3종(커스텀 커맨드, 스킬, 스타터 킷 문서)을 만듭니다. 각 미션은 요구사항과 완성 기준(Definition of Done)만 제시합니다. 구현 경로는 자유입니다.
배경 지식 한 장: 커스텀 커맨드는 스킬로 통합되었습니다.
.claude/skills/이름/SKILL.md를 만들면 /이름 명령이 생기고
(구 .claude/commands/이름.md도 계속 동작), 스킬 디렉토리는 파일 감시로 즉시 반영됩니다.
단, 프로젝트에 skills 디렉토리를 처음 만드는 경우라면 세션을 재시작해야 감시가 시작됩니다.
acceptEdits로 전환하고 시작
1분슈퍼랩은 Claude가 파일을 여러 개 만들고 고치므로, 기본 모드로는 승인 요청이 쏟아져 흐름이 끊깁니다.
A3에서 본 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 | 모드는 기본값을, 규칙은 예외를 정한다는 관계를 설명할 수 있는가 | 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줄 강제 |