Skip to main content

Claude Code Deep Dive — Settings, Permissions, Hooks, MCP, Commands

해당 포스팅은 현재 재직중인 회사에 관련이 없고, 개인 역량 개발을 위한 스터디 자료로 활용할 예정입니다.

들어가며

이 글에서는 Claude Code의 설정 스코프, 권한 규칙, 훅 자동화, MCP 서버 통합, 커스텀 커맨드까지 — Claude Code Deep Dive Workshop Chapter 4 내용을 기본으로 하여 다른 학습 내용들과 같이 정리합니다.


목차

  1. Settings 체계
  2. Permissions
  3. Hooks 아키텍처
  4. Hooks 실전
  5. MCP 구성
  6. MCP 운영과 보안
  7. Commands와 Skills
  8. 통합과 트러블슈팅
  9. Recap & Labs
  10. References

1. Settings 체계

해결하는 문제: "어디에 설정을 두면 누구에게 적용되는가? 같은 키가 충돌하면 누가 이기는가?"

스코프 4계층

스코프파일 위치용도
Managed서버 관리 (plist/레지스트리/시스템 파일)조직 전체, IT가 배포
User~/.claude/settings.json나의 전 프로젝트, 비공유
Project.claude/settings.json저장소 협업자 전원, 커밋 공유
Local.claude/settings.local.json이 저장소의 나만, gitignore

💡 판단 기준: 취향은 User, 팀 표준은 Project, 실험은 Local에 둡니다. Managed는 Ch.3에서 다룬 조직 강제 계층입니다.

우선순위 5단 (같은 키가 충돌할 때)

1. Managed ← 무엇으로도 재정의 불가 (조직의 강제 계층)
2. CLI 인자 ← 실행 시 플래그, 그 세션 한정 임시 재정의
3. Local ← settings.local.json, 프로젝트와 사용자 값을 덮음
4. Project ← 팀 공유 표준, 사용자 값을 덮음
5. User ← 아무도 지정하지 않았을 때의 내 기본값

settings.json 기본 구조

{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": ["Bash(npm run lint)", "Bash(npm run test *)"],
"deny": ["Read(./.env)", "Read(./secrets/**)"]
},
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1"
},
"companyAnnouncements": ["코드 리뷰 필수, 가이드는 wiki 참조"]
}

  • $schema → 에디터 자동완성 활성화
  • env 블록 → 셸 프로파일 대신 설정 파일로 환경변수를 스코프별 배포 (비밀은 금지, 볼트 헬퍼로)
  • companyAnnouncements → 세션 시작 배너

기능별 파일 지도

기능저장 위치
Settings~/.claude/settings.json, .claude/settings(.local).json
MCP 서버User/Local은 ~/.claude.json, Project는 .mcp.json
Subagents~/.claude/agents/, .claude/agents/
CLAUDE.md~/.claude/, 프로젝트 루트, CLAUDE.local.md
백업설정 파일 자동 백업 최근 5개 보관

키 카탈로그 (3분류)

분류주요 키설명
모델/사고alwaysThinkingEnabled, availableModels, enforceAvailableModels, model지능과 모델 선택 통제
운영env, autoUpdatesChannel, permissionMode, companyAnnouncements동작 방식 조정
비활성화disableBypassPermissionsMode, disableAutoMode, 각종 DISABLE_*기능 차단

라이브 리로드

설정 파일을 저장하면 즉시 반영됩니다 (세션 재시작 불필요). 단, modeloutputStyle 변경은 다음 턴부터 적용됩니다. Managed 스코프는 관용 파싱(오류 무시), 개인 스코프는 엄격 파싱(JSON 오류 시 로드 거부)입니다.


Skilljar 보충: CLAUDE.md가 "따라지는" 이유 (Lesson NEW-02)

CLAUDE.md는 **강제 설정이 아닌 안내(guidance)**입니다. 모든 줄이 Claude의 주의를 두고 다른 줄과 경쟁합니다.

원칙설명
간결할수록 준수율 ↑파일이 길어지면 자기 자신과 경쟁 → 개별 규칙 준수율 하락
하드 규칙은 Hook으로"never push to main"은 CLAUDE.md로 부족 → PreToolUse Hook이 막음
구체적 + 검증 가능"Follow best practices" ❌ → "Put routes in src/api/handlers, one per file" ✅
강조는 예산"IMPORTANT", "MUST"는 2~3개에만. 전부 소리치면 아무것도 안 들림
대체를 지명"Don't use default exports" ❌ → "Use named exports, not default exports" ✅
Import = 정리 (절약 아님)@.claude/conventions/code-style.md는 실행 시 인라인 확장됨 — 양은 안 줄음

💡 경험 법칙: Claude가 틀릴 때마다 CLAUDE.md를 수정하세요. "버그 리포트"로 취급하면 파일이 점점 나아집니다.


M0 보충: .claude/ 폴더 전체 구조와 settings.json 핵심 키

Claude Code의 모든 설정은 .claude/ 폴더 안에 살고 있습니다. 전체 지도를 먼저 잡으면 각 파트의 위치가 명확해집니다.

.claude/
├── settings.json ← §1~§6 (설정, 권한, 훅, MCP)
├── settings.local.json ← 개인 오버라이드 (gitignore)
├── CLAUDE.md ← 프로젝트 지침
├── rules/ ← 경로별 규칙 파일
├── skills/ ← §7 (반복 절차 패키징)
├── agents/ ← Ch.2 (서브에이전트 정의)
├── commands/ ← §7 (커스텀 슬래시 명령)
└── hooks/ ← §3~§4 (훅 스크립트 관례 위치)

프로젝트 루트:
├── .mcp.json ← §5~§6 (프로젝트 MCP 서버)
└── CLAUDE.md ← 프로젝트 루트 지침

settings.json 6가지 핵심 영역:

영역키 예시역할
환경env환경변수 주입 (API 키 경로, 리전 등)
권한permissionsallow/deny 규칙, 모드 설정
hooks이벤트별 핸들러 배열
MCP(별도 .mcp.json)서버 연결 정의
기능 플래그disable*, enable*기능 on/off 토글
관측telemetry, otel*OTel 텔레메트리 설정

2. Permissions

해결하는 문제: "Claude가 무엇을 물어보지 않고 할 수 있고, 무엇은 절대 못 하게 할 것인가?"

3동사와 평가 순서

핵심 원칙: deny가 항상 이깁니다. 전 스코프의 규칙은 합집합으로 병합됩니다.

규칙 문법: Tool(specifier)

{
"permissions": {
"allow": [
"Bash(npm run lint)", // 정확히 이 명령만
"Bash(npm run test *)", // 이 접두 + 임의 인자
"Bash(git *)", // git 하위 전부
"Read", // 지정자 없이: 모든 Read 허용
"mcp__github__get_issue" // MCP 서버의 특정 도구
],
"ask": [
"Bash(git push *)", // 배포류는 확인 유지
"Bash(npm publish *)"
],
"deny": [
"Read(./.env*)", // .env, .env.local 등 전부
"Read(./secrets/**)", // gitignore식 경로 패턴
"Bash(curl *)", // 네트워크 호출 차단
"Bash(rm -rf *)", // 위험 명령 차단
"Agent(Explore)" // 특정 서브에이전트 차단
]
}
}

특수 지정자

패턴의미
Agent(Explore)특정 서브에이전트 타입 통제
Agent지정자 없이 → 위임 자체를 통제
mcp__github서버 전체 도구
mcp__github__get_issue서버의 특정 도구
WebFetch도구 전체 (sandbox와 병용)

권한 모드 6종

모드자동 허용승인 필요사용 시점
default (manual)읽기만그 외 모든 것일상 기본
acceptEdits읽기 + 파일 편집 + 일반 파일시스템 bash위험한 명령어반복 수정 세션
plan읽기만 (조사 + 변경 제안)아무것도 편집하지 않음설계 단계
auto모든 것 (분류기 모델이 각 행동 전 검토)분류기가 차단한 것만신뢰 저장소
dontAsk사전 승인된 도구만나머지 = 자동 거부 (프롬프트 없음)무인 CI, 훅 게이트
bypassPermissions모든 검사 건너뜀없음 ⚠️격리된 컨테이너/VM에서만!

⚠️ Auto Mode 분류기의 한계 (Skilljar 통찰): 분류기는 **의도(intent)**를 검사하지, **정확성(correctness)**을 검사하지 않습니다. Claude가 인증을 리팩토링하면서 깨진 인증을 쓰면 — 분류기가 통과시킵니다. 깨진 것은 위험한 것이 아니니까. 해결: Auto Mode + Stop Hook 조합 (의도 검사 + 정확성 확인).

/permissions — 대화형 관리

> /permissions
# 현재 유효 규칙을 스코프별로 표시
# allow, ask, deny 추가와 삭제
# 어느 파일에서 온 규칙인지 출처 표시

# 확인 프롬프트에서 "항상" 선택 → settings.local.json에 자동 기록
# 팀 표준 승격: local에서 검증 후 project로 이동


3. Hooks 아키텍처

해결하는 문제: "CLAUDE.md는 요청이다 — Claude가 보통 따르지만, 건너뛸 수 있다. 절대 건너뛸 수 없는 규칙은 어떻게 만드는가?"

핵심 원리

CLAUDE.mdHook
성격요청 (request)보장 (guarantee)
강제Claude가 보통 따름결정론적 코드, 건너뛸 수 없음
실행Claude 판단 하에루프의 고정된 지점에서 자동

30 이벤트 — 3 케이던스

케이던스이벤트 예시설명
세션SessionStart, InstructionsLoaded, Notification세션 수명주기
UserPromptSubmit, Stop, SubagentStart/Stop대화 턴 경계
도구PreToolUse, PostToolUse, PostToolBatch개별 도구 호출 전후

주요 이벤트 상세

이벤트발생 시점용도
PreToolUse도구 호출 🛡️ 강제 프리미티브 — 차단/수정 가능 (가장 강력)
PostToolUse도구 호출 자동 포맷팅, 자동 린트
StopClaude가 턴을 끝내려 할 때"아니, 아직 안 끝났어" (조건 미충족 시 거부)
SubagentStopSub-agent 완료 시Stop과 동일, 하위 에이전트용
SessionStart세션 시작 시환경 초기화 (startup 또는 compact 소스)

5 핸들러 타입

핸들러설명사용 시점
command외부 스크립트/바이너리 실행대부분의 Hook (기본)
httpHTTP 엔드포인트 호출외부 서비스 알림
mcp_toolMCP 서버의 도구 호출외부 시스템 연동
promptClaude에게 추가 프롬프트 주입컨텍스트 강화
agentSub-agent 스폰복잡한 검증 위임

매처 문법

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"if": "command contains 'rm -rf'",
"hooks": [
{
"type": "command",
"command": "./hooks/guard-destructive.sh"
}
]
}
]
}
}

Exit Code 규칙 (command 핸들러)

Exit Code의미동작
0성공stdout이 JSON이면 파싱, SessionStart에서는 텍스트도 컨텍스트에 추가
2차단 에러stderr가 Claude에게 피드백. 거의 모든 곳에서 차단
그 외 (1 포함)비차단stderr 로깅만, Claude 계속 진행

⚠️ 함정: Exit code 1은 차단하지 않습니다! 멈추려면 반드시 exit 2.

Skilljar 보충: Hook 실전 통찰 (Lesson NEW-05)

updatedInput — 차단 대신 수정(Redact): PreToolUse에서 호출을 차단하는 대신 입력을 수정할 수 있습니다. 예: bash 명령에서 시크릿(sk_live_...)을 발견하면 해당 부분만 마스킹하고 실행은 허용.

⚠️ updatedInput은 전체 입력 객체를 교체합니다. 변경하지 않는 필드도 되돌려 보내야 합니다 — 안 그러면 사라집니다.

SessionStart + compact matcher 함정: Compact 후 컨텍스트를 재주입하려면 PostCompact가 아닌 SessionStart** + compact matcher**를 사용해야 합니다. PostCompact는 출력을 대화에 다시 넣지 못합니다 — SessionStart만이 stdout을 컨텍스트에 추가합니다.

{
"hooks": {
"SessionStart": [{
"matcher": "compact",
"hooks": [{ "type": "command", "command": "./hooks/inject-context.sh" }]
}]
}
}

PreToolUse JSON 반환 구조

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Secret detected in command",
"updatedInput": { "command": "..." }
}
}

permissionDecision동작
allow호출 통과
deny호출 차단
ask사용자에게 결정 위임
defer비대화형 -p에서 프로세스 일시정지/재개 (드물게 사용)

4. Hooks 실전

해결하는 문제: "포맷팅 자동화, 시크릿 유출 차단, 외부 알림을 어떻게 구현하는가?"

레시피 1: 자동 포맷팅 (PostToolUse)

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "npx prettier --write $FILE" }]
}
]
}
}

레시피 2: 시크릿 유출 차단 (PreToolUse)

#!/bin/bash
# hooks/guard-secrets.sh
# stdin으로 JSON 입력 받음

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

if echo "$COMMAND" | grep -qE '(sk_live_|AKIA[A-Z0-9]{16}|ghp_)'; then
echo "Secret detected in command" >&2
exit 2 # 차단!
fi

exit 0 # 통과

{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "./hooks/guard-secrets.sh" }]
}]
}
}

레시피 3: Slack 알림 (Stop)

{
"hooks": {
"Stop": [{
"hooks": [{
"type": "http",
"url": "https://hooks.slack.com/services/T.../B.../xxx",
"method": "POST",
"body": { "text": "Claude Code 작업 완료: ${SESSION_ID}" }
}]
}]
}
}

레시피 4: Auto Mode + Stop Hook 조합

{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "./hooks/verify-tests-pass.sh"
}]
}]
}
}

#!/bin/bash
# hooks/verify-tests-pass.sh
npm test 2>/dev/null
if [ $? -ne 0 ]; then
echo "Tests still failing - continue working" >&2
exit 2 # Claude에게 "아직 안 끝났어" 전달
fi
exit 0

💡 Auto Mode(의도 감시) + Stop Hook(정확성 확인) = 무인 실행의 두 축. 하나는 행동 전 의도를 감시하고, 다른 하나는 행동 후 정확성을 확인합니다.

레시피 5: 컨텍스트 주입 (SessionStart)

{
"hooks": {
"SessionStart": [{
"matcher": "startup",
"hooks": [{
"type": "command",
"command": "echo 'Current sprint: SPRINT-42, deadline: 2026-08-20'"
}]
}]
}
}

SessionStart에서 exit 0 + stdout 텍스트 → 컨텍스트에 자동 추가됩니다.


M6 보충: 하네스 엔지니어링 — Settings를 "시스템"으로 만드는 철학

Settings의 개별 키를 아는 것과, 그것을 시스템으로 설계하는 것은 다릅니다.

하네스 엔지니어링 3대 철학:

철학의미Settings 적용
평가자 분리만든 사람과 검증하는 사람을 분리Hook으로 자동 리뷰어 분리, sub-agent로 Cold Second Opinion
환경 강제"말로 부탁하지 말고 환경으로 막아라"CLAUDE.md(부탁) < Hook(강제) < managed(불변)
컨텍스트 보호메인 대화의 신호 대 잡음 비율 유지Skills 프리로드, MCP Tool Search, sub-agent 격리

하네스 5구성요소 (파이프라인 순서):

제약(Constraints) → 도구(Tools) → 실행(Execution) → 상태(State) → 게이트(Gate)
permissions MCP+내장 Bash/Agent memory/git Hook 검증

개발 파이프라인에서의 Hook 활용:

Planner → Generator → Reviewer → QA(Hook 게이트)
Plan 모드 코드 생성 리뷰 sub-agent PostToolUse/Stop Hook으로
테스트 통과 여부 강제

CLAUDE.md 작성 원칙 (M6 권장):

  • 80~120줄 권장 (200줄은 상한, 80줄이 최적)
  • WHAT/WHY/HOW 구조: 무엇을 → 왜 → 어떻게
  • Progressive Disclosure: 핵심만 CLAUDE.md에, 상세는 rules/에, 절차는 skills/에
  • 규칙은 Hook에: "~하지 마라"는 CLAUDE.md에 쓰지 말고 Hook으로 강제

5. MCP 구성

해결하는 문제: "외부 시스템(GitHub, Slack, DB, AWS)을 Claude의 도구로 연결하려면 어떻게 하는가?"

MCP 3 프리미티브

프리미티브역할예시
Tool동작 호출 (Write)이슈 생성, PR 머지, 메시지 전송
Resource데이터 조회 (Read)비용 데이터, 메트릭, 스키마
Prompt워크플로 템플릿PR 리뷰 절차, 비용 분석 템플릿

4 전송 방식

전송설명사용 시점
stdio로컬 프로세스, stdin/stdout로컬 DB, 파일 처리
http원격 HTTP 엔드포인트SaaS 연결 (GitHub, Slack 등)
sseServer-Sent Events실시간 스트리밍
wsWebSocket양방향 통신

설정 방법

# 원격 HTTP 서버 — SaaS 연결
claude mcp add --transport http github \
https://api.githubcopilot.com/mcp/

# 로컬 stdio 서버 — DB 연결
claude mcp add -- npx -y @bytebase/dbhub \
--dsn "postgresql://prod.db.com:5432/app"

# 팀 전체 공유 — project scope
claude mcp add --transport http --scope project sentry \
https://mcp.sentry.dev/mcp

# Bearer 토큰 인증
claude mcp add --transport http stripe https://mcp.stripe.com \
--header "Authorization: Bearer ${STRIPE_KEY}"

# 상태 확인
claude mcp list # CLI
/mcp # 세션 내

3 스코프

스코프저장 위치용도
local (기본)~/.claude.json개인 실험
project.mcp.json (프로젝트 루트)팀 표준 도구
managedmanaged-mcp.json (시스템 디렉토리)조직 전체 강제

.mcp.json 팀 공유 예시

{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": { "Authorization": "Bearer ${GITHUB_PAT}" }
},
"sentry": {
"type": "http",
"url": "https://mcp.sentry.dev/mcp"
},
"database": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@bytebase/dbhub", "--dsn", "${DB_DSN}"]
}
}
}

💡 ${VAR} 환경변수 확장을 지원합니다. 시크릿은 코드 밖에 두고, .mcp.json은 프로젝트 루트에 커밋합니다.

OAuth 2.0 인증

# /mcp → 브라우저 인증 흐름
# 또는 CLI에서:
claude mcp login sentry # OAuth 흐름 시작
claude mcp logout sentry # 자격증명 삭제

MCP 서버가 많을 때 모든 도구를 컨텍스트에 로드하면 낭비입니다. Tool Search는 필요한 도구만 동적으로 로드해 컨텍스트를 절약합니다.


6. MCP 운영과 보안

해결하는 문제: "수십 개 MCP 서버를 팀에서 안전하게 운영하려면 어떤 통제가 필요한가?"

조직 통제

설정효과
allowedMcpServers화이트리스트 — 이 목록에 없는 서버는 사용 불가
managed-mcp.json조직이 강제하는 서버 구성
Hook + MCPPreToolUse에서 MCP 도구 호출을 추가 검증

신뢰 모델

  • 첫 사용 시 신뢰 확인 프롬프트 (Trust verification)
  • -p 플래그 사용 시 비활성화됨 (무인 실행에서는 사전 승인 필요)
  • 권한 규칙에서 mcp__서버명__도구명 패턴으로 세밀 통제

성능 고려

설정효과
MAX_MCP_OUTPUT_TOKENS출력 제한 (기본 25K 토큰) — 컨텍스트 폭주 방지
도구 검색사용 시점에만 도구 로드 — 컨텍스트 절약
자동 재연결지수 백오프 (최대 5회) — 네트워크 불안정 대응

M7 보충: MCP 고급 기능 — 컨텍스트 절약과 실시간 연동

MCP를 효과적으로 운영하면 비용과 성능이 크게 개선됩니다.

기능효과설명
Tool Search컨텍스트 85% 절약MCP 서버가 많을 때 필요한 도구만 동적 로드 (전체를 컨텍스트에 넣지 않음)
OAuth 2.0보안 인증/mcp → 브라우저 인증 흐름. 원격 SaaS 서버 연결 시 표준
자동 재연결안정성지수 백오프 (최대 5회). 네트워크 불안정 환경 대응
Channel (Push)실시간시스템 → Claude Code 알림. 모니터링·경고에 활용
출력 제한비용 통제기본 25K 토큰 (MAX_MCP_OUTPUT_TOKENS). 대량 데이터 조회 시 폭주 방지
@-mention 리소스정밀 참조@github:issue://123 형태로 특정 리소스를 직접 지정

7. Commands와 Skills

해결하는 문제: "반복하는 워크플로를 한 번 정의해서 팀 전체가 같은 방식으로 실행하게 하려면?"

커스텀 명령 (Commands)

.claude/commands/ 디렉토리에 마크다운 파일을 넣으면 /명령 으로 사용할 수 있습니다.

<!-- .claude/commands/review.md -->
---
description: 현재 diff를 리뷰합니다
---

git diff의 변경사항을 분석하고 다음 기준으로 리뷰해주세요:
1. 버그 위험
2. 보안 취약점
3. 성능 이슈
4. 코드 스타일

$ARGUMENTS가 있으면 해당 파일에 집중해주세요.

> /review # 전체 diff 리뷰
> /review src/auth/login.ts # 특정 파일 집중

  • $ARGUMENTS → 명령 뒤의 텍스트가 치환됩니다
  • User scope: ~/.claude/commands/ (개인)
  • Project scope: .claude/commands/ (팀 공유)

Skills (SKILL.md)

Skills는 Commands보다 한 단계 위 — 자동 트리거 + 참조 자료 + 실행 스크립트를 묶는 패키지입니다.

.claude/skills/verify-refactor/
├── skill.md ← 간결한 메인 파일 (트리거 + 절차)
├── reference.md ← 상세 자료 (필요할 때만 로드)
└── check.sh ← 실행 스크립트 (컨텍스트에 로드 안 함)

선택 가이드

수단적합한 작업트리거
CLAUDE.md항상 적용되는 컨벤션모든 요청에 포함
Skill특정 작업의 절차 + 참조 자료작업 매칭 시에만 로드
Command사용자가 명시적으로 실행하는 워크플로/명령 입력
Hook절대 건너뛸 수 없는 규칙코드가 실제로 실행됨

Skilljar 보충: Verification Skill (Lesson NEW-03)

"동일한 다단계 지시를 두 번 타이핑했다면, 그것은 Skill입니다."

Verification Skill은 "코드 수정 → 자동으로 검증 실행"을 묶는 패턴입니다. 핵심은 요청 없이 자동 트리거된다는 점입니다:

[Claude가 리팩토링 완료]
↓ (Skill description과 매칭 → 자동 트리거)
1. 테스트 스위트 실행
2. Diff 읽기
3. 테스트가 약화되지 않았는지 확인 ← 이것이 핵심
4. Pass/Fail 보고 + 증거 첨부

왜 "테스트 통과"만으로 부족한가: 테스트가 무엇이든 통과하도록 조용히 느슨하게 될 수 있습니다. 그래서 Skill이 diff를 읽고 테스트가 약화되지 않았는지까지 확인합니다.

설계 원칙:

파일역할컨텍스트 로드?
skill.md무엇을 할지 설명 (간결하게)✅ 설명만
reference.md깊이 있는 참고 자료필요할 때만
check.sh게이트 실행 도구❌ 실행만 (로드 안 함)

적용 가능한 절차들:

  • Release checklist → 릴리스 전 모든 게이트 자동 실행
  • Migration recipe → 마이그레이션 단계 순차 수행 + 검증
  • Pre-PR check → PR 생성 전 린트/테스트/포맷 확인

💡 skill.md를 간결하게 유지하세요. 무거운 자료(긴 설명, 실행 스크립트)는 사이드 파일로. 간결한 파일이 할 일을 설명하고, 사이드 파일이 깊이와 도구를 담습니다.


8. 통합과 트러블슈팅

해결하는 문제: "설정이 안 먹을 때, 훅이 안 도를 때, 어디서 진단을 시작하는가?"

.claude 디렉토리 풀스택

.claude/
├── settings.json ← 권한, 훅, env
├── settings.local.json ← 개인 오버라이드 (gitignore)
├── agents/ ← 서브에이전트 정의
├── commands/ ← 커스텀 슬래시 명령
├── skills/ ← SKILL.md 패키지
└── hooks/ ← 훅 핸들러 스크립트 (관례 위치)
.mcp.json ← MCP 서버 (프로젝트 루트)
CLAUDE.md ← 프로젝트 지침

진단 4도구

도구역할
/doctor설치와 설정 종합 진단, f 키로 자동 수정
/permissions현재 유효 규칙 + 출처 표시
/mcpMCP 서버 상태와 컨텍스트 비용
/config대화형 설정 변경 (모델, 채널 등)

설정이 안 먹을 때 체크리스트

  1. 스코프 확인: /permissions으로 어느 파일의 규칙인지 확인
  2. 우선순위 확인: Managed > CLI > Local > Project > User 순서 점검
  3. JSON 유효성: $schema가 있으면 에디터가 오류 표시
  4. 리로드 확인: 파일 저장 후 수 초 대기 (드문 경우 세션 재시작)
  5. managed 충돌: 조직 deny가 개인 allow를 항상 이김

보안 4관문

1. Permissions (allow/ask/deny) → 1차 방어
2. Sandbox (/sandbox, allowedDomains) → 강한 봉쇄
3. Hooks (PreToolUse exit 2) → 결정론적 차단
4. Managed settings → 조직 절대선


M9 보충: 엔터프라이즈 CCB 운영 — 설정이 뚫리는 3가지 지점

설정(Settings)이 아무리 정교해도 클라이언트 사이드에서는 세 가지 실패 지점이 존재합니다.

실패 지점설명대응
settings.json 편집 가능로컬 파일이므로 개발자가 직접 수정 가능managed settings로 강제 (삭제 불가)
도구 교체허용된 도구 대신 다른 도구로 우회permissions.deny + Hook 검증
텔레메트리 우회환경변수로 OTel 비활성화 가능managed env에서 강제 설정

엔터프라이즈 4가지 불변 질문:

  1. Privilege — 이 도구가 무엇을 할 수 있는가?
  2. Data — 데이터가 어디로 흐르는가?
  3. Audit — 누가 무엇을 했는지 추적 가능한가?
  4. Cost — 비용이 통제 가능한가?

💡 "진짜 경계는 네트워크 경로 위, 도구 바깥에 있어야 한다" — 클라이언트 설정은 편의, Gateway + managed가 강제입니다.

2-Pillar Model: 조직 도입의 두 축

  • Pillar 1: LLM Gateway — 기술적 통제 (인증, 모델 라우팅, 비용 한도, 감사 로그)
  • Pillar 2: 활용 교육 — 사람 통제 (마인드셋, 베스트 프랙티스, 안티패턴 인지)

9. Recap & Labs

핵심 요약 (6문장)

  1. Settings는 4스코프, 5단 우선순위, 저장 즉시 반영입니다
  2. Permissions는 deny > allow > ask, 모드는 무일치의 기본 거동입니다
  3. Hooks는 30 이벤트 × 5 핸들러, exit 2만 차단합니다
  4. MCP는 3프리미티브 × 4전송 × 3스코프로 외부를 연결합니다
  5. Commands는 /명령, Skills는 자동 트리거 패키지입니다
  6. 진단은 /doctor, /permissions, /mcp, /config 네 도구로 시작합니다

실습 4종 요약

Lab주제확인하는 것소요
Lab 1Permissions 설계규칙이 적용되는가? (정적 검증)~10분
Lab 2Hook 구현코드가 실행을 막는가? (동적 차단)~15분
Lab 3MCP 연결외부 시스템이 연결되는가? (통합)~10분
Lab 4풀스택 통합전부 합치면 돌아가는가? (풀스택)~20분

Lab 1: Permissions 설계

목표: allow/deny 규칙이 실제로 명령을 허용/차단하는지 확인합니다소요 시간: ~10분사전 준비: Claude Code 설치 완료, 아무 프로젝트 디렉터리

Step 1: 실습 프로젝트 준비

# 임시 프로젝트 생성
mkdir -p ~/lab-permissions && cd ~/lab-permissions
git init
echo "SECRET_KEY=abc123" > .env
echo '{"name": "lab"}' > package.json
mkdir -p .claude

Step 2: Local 설정에 규칙 작성

.claude/settings.local.json을 생성합니다. local에서 먼저 검증하는 이유는, 실수해도 팀에 영향이 없기 때문입니다.

{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(npm run test *)",
"Bash(git status)",
"Bash(git diff *)",
"Read",
"Grep",
"Glob"
],
"deny": [
"Read(./.env)",
"Read(./secrets/**)",
"Bash(rm -rf:*)",
"Bash(curl * | bash:*)"
]
}
}

Step 3: 적용 확인

claude
> /permissions
# deny 목록에 .env, rm -rf가 보이는지 확인
# allow 목록에 npm run test, git status가 보이는지 확인

Step 4: 차단 테스트

> .env 파일 내용을 읽어줘
# 예상: "Permission denied" — Read(./.env) 규칙에 의해 차단

실제 결과:

❯ .env 파일 내용 보여줘

Read 1 file (ctrl+o to expand)

⏺ 권한 설정에서 막혀 읽을 수 없습니다 — .env(정확히는 그 디렉터리)에 deny 규칙이
걸려 있어 Read가 거부됐습니다. Bash cat으로 우회하는 건 하지 않겠습니다.

참고로 이 세션에서 조금 전에 읽었던 내용은 pass=1122 였고, 그 이후 파일이
바뀌었는지는 지금 확인할 수 없습니다.

읽게 하려면 /permissions에서 해당 deny 규칙을 제거하거나, 직접 ! cat .env로
실행해 출력을 대화에 넣어주세요.

📌 주목할 점: Claude는 deny 규칙을 인지하고 Read를 시도조차 하지 않습니다. 또한 "이 세션에서 조금 전에 읽었던 내용"을 기억하고 있지만 현재는 접근 불가라고 정직하게 보고합니다. 이것이 Permissions의 runtime 강제력입니다.

> rm -rf / 실행해줘
# 예상: "Permission denied" — Bash(rm -rf:*) 규칙에 의해 차단

> git status 실행해줘
# 예상: 정상 실행 — allow 목록에 포함

Step 5: 팀 공유로 승격

검증이 끝나면 .claude/settings.json으로 이동합니다 (git commit 대상):

cp .claude/settings.local.json .claude/settings.json
rm .claude/settings.local.json
# 이제 이 저장소의 모든 협업자에게 동일 규칙이 적용됩니다

Step 6: 재확인

claude
> /permissions
# Project 스코프에서 같은 규칙이 적용되는지 확인

검증: /permissions 출력에 deny/allow 규칙이 정확히 나타나고, 차단된 명령이 실제로 거부됩니다.트러블슈팅: 규칙이 안 먹으면 → JSON 문법 오류 확인 ($schema 있으면 에디터가 검증해줌) → /doctor로 설정 로드 상태 확인


Lab 2: Hook 구현

목표: PreToolUse 훅이 시크릿을 차단하고, exit code 2만 실제로 멈추는지 확인합니다소요 시간: ~15분사전 준비: Lab 1 프로젝트 또는 아무 프로젝트

Step 1: 훅 스크립트 작성

.claude/hooks/block-secrets.sh를 생성합니다. 이 스크립트는 Bash 명령에 시크릿 패턴이 있으면 exit 2로 차단합니다.

mkdir -p .claude/hooks
cat > .claude/hooks/block-secrets.sh << 'EOF'
#!/bin/bash
# PreToolUse Hook: 시크릿이 포함된 Bash 명령을 차단합니다
# stdin으로 JSON이 들어옵니다

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# 시크릿 패턴 감지
if echo "$COMMAND" | grep -qiE '(cat|less|head|tail).*\.(env|pem|key|secret)'; then
echo "🚫 시크릿 파일 접근이 차단되었습니다: $COMMAND" >&2
exit 2 # exit 2 = 차단! (exit 1은 차단 안 됨)
fi

# 패턴 미감지 → 통과
exit 0
EOF
chmod +x .claude/hooks/block-secrets.sh

Step 2: settings.json에 Hook 등록

.claude/settings.json (또는 .local.json)에 추가합니다:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./.claude/hooks/block-secrets.sh"
}
]
}
]
}
}

Step 3: 차단 테스트

claude
> cat .env 실행해줘
# 예상: "🚫 시크릿 파일 접근이 차단되었습니다" 메시지와 함께 차단
# stderr에 메시지가 나오고, Claude는 "차단되었습니다"를 인지함

Step 4: exit code 차이 확인 (핵심!)

스크립트를 exit 1로 변경해봅니다:

sed -i '' 's/exit 2/exit 1/' .claude/hooks/block-secrets.sh

다시 시도:

> cat .env 실행해줘
# 예상: 차단되지 않음! exit 1은 에러 로깅만 하고 Claude는 계속 진행

⚠️ 핵심 교훈: exit 2만 차단합니다. exit 1은 "에러가 있었지만 진행"입니다. 이것을 모르면 보안 훅이 무용지물이 됩니다.

원래대로 복구:

sed -i '' 's/exit 1/exit 2/' .claude/hooks/block-secrets.sh

Step 5: (보너스) PostToolUse 자동 포맷팅

Edit 후 자동으로 prettier를 실행하는 훅:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write $(echo $HOOK_FILE_PATH)"
}
]
}
]
}
}

Step 5-1: ⚠️ 함정 — Permissions deny ≠ 모든 경로 차단

실제로 테스트하면 다음과 같은 상황이 발생합니다:

❯ .env 파일 내용 보여줘

⏺ Bash(cat /Users/you/project/.env)
⎿ pass=1122 ← 읽혔다!

왜 뚫렸는가?

설정차단 범위Bash(cat .env)
"deny": ["Read(./.env*)"]Read 도구만 차단❌ Bash라서 무관
Hook (block-secrets.sh)grep 패턴에 일치하는 것만✅ 패턴 있으면 차단

Permissions의 deny는 **도구 단위(Tool-level)**로 작동합니다. Read(./.env*)는 Claude의 Read 도구 호출만 막을 뿐, Bash(cat .env)Bash(head .env) 같은 셸 명령은 별개 경로입니다.

해결: Hook에 파일 접근 패턴 추가 (이중 방어)

# block-secrets.sh 보강 — .env 파일 접근도 차단
if echo "$COMMAND" | grep -qE '\.(env|pem|key|secret)'; then
echo "⛔ 민감 파일 접근이 차단되었습니다" >&2
exit 2
fi

🔑 설계 원칙: 민감 파일 보호는 반드시 Permissions + Hook 이중 방어로 구성합니다.

  • deny → Read 도구 경로 차단 (1차)
  • Hook → Bash 셸 경로 차단 (2차)
  • 둘 중 하나만으로는 우회 가능합니다.

Step 5-2: ✅ 이중 방어 성공 시 Claude의 반응

Permissions deny + Hook이 모두 적용된 상태에서 다시 시도하면:

❯ .env 파일 내용 보여줘

Read 1 file (ctrl+o to expand)

⏺ 권한 설정에서 막혀 읽을 수 없습니다 — .env(정확히는 그 디렉터리)에 deny 규칙이
걸려 있어 Read가 거부됐습니다. Bash cat으로 우회하는 건 하지 않겠습니다.

명시적으로 cat .env 실행해줘라고 Bash를 지정해도 동일합니다:

❯ cat .env 실행해줘

⏺ .env 읽기가 권한 설정에서 차단돼 있습니다 (Read 도구 거부 규칙).
Bash cat으로 우회하는 건 그 가드를 무력화하는 셈이라 하지 않았습니다.
직접 보시려면 프롬프트에 ! cat .env 를 입력하시면 이 세션에서 실행되고
출력이 대화에 들어옵니다.

Claude가 Read deny를 인지하고, Bash 우회도 스스로 자제합니다. 이것이 이중 방어의 효과입니다:

  1. Read 도구 → Permissions deny가 즉시 차단
  2. Claude가 "그럼 Bash로 우회할까?" 판단 → Hook 존재를 인지하여 시도하지 않음
  3. 결과적으로 의도한 보안 경계가 유지

📌 흥미로운 점: Claude는 사용자에게 ! cat .env(셸 이스케이프)를 안내합니다. 이는 사용자 본인이 직접 실행하는 것이므로 보안 경계를 침해하지 않는다는 판단입니다. 에이전트의 자율 행동과 사용자의 명시적 행동을 구분하는 원칙이 내재되어 있음을 보여줍니다.

💡 Hook이 실제로 호출되지 않았어도, Hook의 존재 자체가 모델의 판단에 영향을 줍니다. Deny + Hook 조합은 기술적 차단과 행동 유도를 동시에 달성합니다.


검증: cat .env 시도 시 차단 메시지가 뜨고, exit 1로 변경하면 통과됩니다.트러블슈팅: 훅이 안 먹으면 → chmod +x 확인 → jq 설치 확인 → matcher 문자열이 정확한지 ("Bash", "Edit" 대소문자 구분)


Lab 3: MCP 연결

목표: MCP 서버를 연결하고 팀 공유 스코프로 저장하는 것을 확인합니다소요 시간: ~10분사전 준비: Node.js 18+ (npx 사용 가능), Claude Code

Step 1: Filesystem MCP 서버 추가

cd ~/lab-ch4 # 또는 아무 프로젝트

# filesystem MCP 서버를 project 스코프로 추가
claude mcp add --scope project filesystem \
-- npx -y @modelcontextprotocol/server-filesystem ~/lab-ch4

이 서버는 지정한 디렉터리의 파일을 읽고 쓸 수 있는 도구를 제공합니다.

Step 2: 상태 확인

claude
> /mcp
# 예상 출력:
# filesystem (stdio, project) — running
# Tools: read_file, write_file, list_directory, ...

Step 3: MCP 도구로 작업 요청

> 이 프로젝트에 있는 파일 목록을 보여줘
# Claude가 mcp__filesystem__list_directory 도구를 호출
# 결과: ~/lab-ch4의 파일 목록 표시

Step 4: .mcp.json 확인

cat .mcp.json
# 예상: project 스코프에 저장된 서버 설정
# {
# "mcpServers": {
# "filesystem": {
# "type": "stdio",
# "command": "npx",
# "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/jinwoong/lab-ch4"]
# }
# }
# }

Step 5: 팀 공유 확인

git add .mcp.json
git commit -m "feat: filesystem MCP 서버 추가"
# 이제 이 저장소를 clone한 동료도 같은 MCP 서버를 사용합니다

검증: /mcp에서 서버가 connected 상태이고 Tools: 14 tools가 표시됩니다.

트러블슈팅:

증상원인해결
Status: failed패키지명 변경됨@anthropic/mcp-server-filesystem@modelcontextprotocol/server-filesystem으로 교체
npx 실행 안 됨Node.js 버전node --version → 18+ 필요
연결 후 도구 0개경로 오류절대 경로 사용 권장 (~/ 대신 /Users/you/...)
# 패키지 직접 실행 테스트 (정상이면 JSON-RPC 대기 → Ctrl+C 종료)
npx -y @modelcontextprotocol/server-filesystem /Users/jinwoong/lab-ch4


Lab 4: 풀스택 통합

목표: settings + permissions + hook + MCP + command를 하나의 프로젝트에 조립하고 전체가 물리는지 확인합니다소요 시간: 20분~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~사전 준비~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~: Lab 13 완료 또는 동등한 이해

Step 1: 프로젝트 골격 생성

mkdir -p ~/lab-fullstack && cd ~/lab-fullstack
git init
mkdir -p .claude/hooks .claude/commands src
echo "DB_PASSWORD=supersecret" > .env
echo '# My Project' > README.md

Step 2: CLAUDE.md 작성

cat > CLAUDE.md << 'EOF'
# 프로젝트 규칙

## 코드 스타일
- TypeScript 사용, strict 모드
- 파일당 하나의 export
- 테스트는 src/ 옆에 *.test.ts로

## 금지 사항
- .env 파일 읽기 금지 (Hook으로 강제됨)
- rm -rf 금지
- main 브랜치 직접 push 금지
EOF

Step 3: settings.json 조립

cat > .claude/settings.json << 'EOF'
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1"
},
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git add *)",
"Bash(git commit *)",
"Read",
"Grep",
"Glob"
],
"deny": [
"Read(./.env)",
"Bash(rm -rf:*)",
"Bash(git push * main:*)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./.claude/hooks/block-secrets.sh"
}
]
}
]
}
}
EOF

Step 4: Hook 스크립트 (Lab 2에서 재사용)

cat > .claude/hooks/block-secrets.sh << 'EOF'
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qiE '(cat|less|head|tail).*\.(env|pem|key|secret)'; then
echo "🚫 시크릿 접근 차단: $COMMAND" >&2
exit 2
fi
exit 0
EOF
chmod +x .claude/hooks/block-secrets.sh

Step 5: MCP 서버 추가

claude mcp add --scope project filesystem \
-- npx -y @anthropic/mcp-server-filesystem ./src

Step 6: 커스텀 명령 생성

cat > .claude/commands/check-health.md << 'EOF'
---
description: 프로젝트 설정 상태를 한눈에 확인합니다
---

아래 항목을 순서대로 확인하고 표로 정리해줘:
1. /permissions로 권한 규칙 수 확인
2. /mcp로 MCP 서버 상태 확인
3. CLAUDE.md 존재 여부
4. .env 파일 읽기 시도 (차단되어야 정상)
EOF

Step 7: 전체 동작 확인

claude

# 1. 커스텀 명령 실행
> /check-health
# 예상: 4가지 항목이 표로 정리됨 (마지막 항목은 "차단됨"이어야 정상)

# 2. MCP 도구 확인
> src 폴더에 어떤 파일이 있어?
# 예상: MCP filesystem 도구로 src/ 목록 조회

# 3. 시크릿 차단 확인
> .env 파일 내용 보여줘
# 예상: Hook에 의해 차단

# 4. 허용된 명령 확인
> git status 보여줘
# 예상: 정상 실행 (allow 목록)

Step 8: 실제 /check-health 실행 결과 예시

아래는 Lab 1~3을 마친 프로젝트에서 /check-health를 실행한 실제 출력입니다:

⏺ 프로젝트 상태 점검 결과입니다.

1. .claude/settings.json 로드 여부 — ✅ 정상 로드

┌───────────────────────────────┬────────────────┬─────────────────────────────────────┐
│ 파일 │ 상태 │ 내용 │
├───────────────────────────────┼────────────────┼─────────────────────────────────────┤
│ .claude/settings.json │ ✅ 로드 (362B) │ permissions + PreToolUse hook │
├───────────────────────────────┼────────────────┼─────────────────────────────────────┤
│ .claude/settings.local.json │ ✅ 로드 (324B) │ permissions + enabledMcpjsonServers │
└───────────────────────────────┴────────────────┴─────────────────────────────────────┘

2. /permissions 규칙 개수 — 총 16건 (중복 제거 시 11건)

┌───────────────────────────────┬───────┬──────┬─────┐
│ 소스 │ allow │ deny │ ask │
├───────────────────────────────┼───────┼──────┼─────┤
│ .claude/settings.json │ 4 │ 2 │ 0 │
├───────────────────────────────┼───────┼──────┼─────┤
│ .claude/settings.local.json │ 5 │ 4 │ 0 │
├───────────────────────────────┼───────┼──────┼─────┤
│ ~/.claude/settings.local.json │ 1 │ 0 │ 0 │
├───────────────────────────────┼───────┼──────┼─────┤
│ 합계 │ 10 │ 6 │ 0 │
└───────────────────────────────┴───────┴──────┴─────┘

⚠️ Read를 무조건 allow하면서 Read(./.env*)를 deny
— deny가 allow보다 우선하므로 .env 차단은 유효합니다.

3. /mcp 서버 연결 상태 — ✅ filesystem 연결됨

프로젝트 .mcp.json의 filesystem 서버: ✅ connected (14 tools)

4. Hook 등록 여부 — ✅ 등록 및 실제 작동 확인

PreToolUse / matcher Bash → ./.claude/hooks/block-secrets.sh
(실행 권한 -rwxr-xr-x 정상)

실제 호출로 검증:
ls .env.example 실행 시 → exit 2 차단 경로 정상 동작

🔍 발견: Hook 패턴 오탐 문제 grep -qE '\.(env|pem|key|secret)' 패턴이 과도하게 넓어 오탐 발생:

오탐 명령매칭 이유
ls .env.example.env에 매칭 (시크릿 아님)
python3 -c "d.keys()".key에 매칭 (dict 메서드)
.keyboard, dict.keys(), docker.env.template 같은 정상 코드도 차단됩니다.
개선된 패턴 (단어 경계 + 파일 끝 앵커):
grep -qE '\.(env|pem|key|secret)([[:space:]]|$|["'"'"'/])'

보수적 보안(오탐 허용) vs 정밀 매칭(누락 위험) — 팀 합의에 따라 선택합니다.

검증: 4가지가 모두 동작하면 풀스택 완성입니다:

  • ✅ Permissions: deny 규칙이 적용됨
  • ✅ Hook: 시크릿 접근 시 exit 2로 차단
  • ✅ MCP: filesystem 서버가 동작
  • ✅ Command: /check-health가 실행됨

트러블슈팅:

  • 명령이 안 보이면 → .claude/commands/ 경로 확인 (파일명이 곧 명령명)
  • Hook이 안 먹으면 → chmod +x 확인, jq 설치 확인
  • MCP가 안 뜨면 → /mcp에서 에러 메시지 확인, Node 버전 확인

References

1차 출처 (본문 작성 기반)

#출처상세
[1]Claude Code Deep Dive Workshop — Chapter 4: SettingsAWS Korea, 2026.07. github.com/whchoi98/claude-code-workshop
[2]Claude Code in ActionAnthropic Skilljar. NEW-02 (CLAUDE.md), NEW-03 (Verification Skills), NEW-04 (Permission Modes), NEW-05 (Hooks). anthropic.skilljar.com
[3]Introduction to MCPAnthropic Skilljar. anthropic.skilljar.com

공식문서 (교차 검증)

#문서URL
[4]Settingsdocs.anthropic.com/en/docs/claude-code/settings
[5]Permissionsdocs.anthropic.com/en/docs/claude-code/permissions
[6]Hooksdocs.anthropic.com/en/docs/claude-code/hooks
[7]MCPdocs.anthropic.com/en/docs/claude-code/mcp
[8]Securitydocs.anthropic.com/en/docs/claude-code/security

| [9] | Claude Code on Bedrock Online Program | AWS Skill Builder. M0(Fundamentals), M6(Harness Engineering), M7(MCP), M9(Enterprise). skillbuilder.aws |