Claude Code Docs 해체 분석 (2): 권한 모드
Claude 공식 문서(permission-modes)를 읽으면서 생긴 의문들을 중심으로 정리했다.
dontAsk, bypassPermissions의 실질적인 사용
권한 프롬프트
Claude에게 작업을 시키면 파일 읽기, 파일 편집, 셸 명령 실행 같은 도구를 호출한다.
이 중 파일 편집이나 셸 명령처럼 영향이 있는 작업을 실행하려 할 때, default 모드에서는 실행 전에 반드시 멈추고 터미널에 이런 화면을 띄운다.
1
2
Claude wants to run the following command: git push origin main
Do you want to allow this? (y/n/always)
1
2
3
4
5
6
7
Claude wants to edit the following file:
src/auth/login.ts
- const token = req.headers.token
+ const token = req.headers.authorization?.split(' ')[1]
Allow this edit? (y/n/always/diff)
이것이 권한 프롬프트. 사용자가 y/n으로 직접 판단해야 Claude가 다음 단계로 넘어간다.
dontAsk, bypassPermissions 차이
dontAsk, bypassPermissions 모두 이 프롬프트를 없앤다는 공통점이 있지만, 방식이 근본적으로 다르다.
dontAsk: 프롬프트 없이 자동으로 실행하되, allow 목록에 있는 것만 실행한다.
목록에 없는 작업은 사용자에게 묻지도 않고 즉시 거부된다.
allow 목록은 settings.json의 permissions.allow 배열이다. 파일 위치는 세 가지다.
| 파일 경로 | 적용 범위 |
|---|---|
~/.claude/settings.json | 내 모든 프로젝트에 적용 |
.claude/settings.json | 현재 프로젝트 (git에 커밋 가능) |
.claude/settings.local.json | 현재 프로젝트 로컬 전용 (git 제외) |
1
2
3
4
5
6
// .claude/settings.json
{
"permissions": {
"allow": ["Bash(npm test)", "Read(*)"]
}
}
목록에 추가하는 방법은 두 가지다.
파일을 직접 편집하거나, default 모드에서 권한 프롬프트가 떴을 때 always를 입력하면 Claude Code가 해당 규칙을 자동으로 settings.json에 저장한다.
이 설정이 있는 상태에서 dontAsk 모드를 켜면:
npm test→ 자동 실행src/index.ts읽기 → 자동 실행git push시도 → 프롬프트 없이 즉시 차단src/index.ts편집 시도 → 프롬프트 없이 즉시 차단
Claude 입장에서는 “이 도구 사용이 거부됐습니다”라는 에러를 받는다.
사용자는 아무 프롬프트도 보지 않는다.
bypassPermissions: 프롬프트도 없고, 거부도 없다. allow 규칙 자체를 포함한 모든 권한 검사가 비활성화된다.
git push든 rm -rf든 뭐든 그냥 실행된다.
| 상황 | default | dontAsk | bypassPermissions |
|---|---|---|---|
Claude가 git push 시도 | 프롬프트 → 사용자 판단 | allow에 없으면 즉시 차단 | 그냥 실행 |
| Claude가 파일 편집 시도 | 프롬프트 → 사용자 판단 | allow에 있으면 자동, 없으면 차단 | 그냥 실행 |
| 안전 검사 주체 | 사용자가 직접 | 사전 정의된 규칙 | 없음 |
즉 dontAsk는 “허용 목록에 있는 것만 된다”이고, bypassPermissions는 “모든 게 된다”다.
bypassPermissions 에 대한 CLI 플래그 차이
아래 두 플래그는 이름이 비슷하지만 동작이 다르다.
--dangerously-skip-permissions: --permission-mode bypassPermissions와 완전히 동일하다.
세션을 즉시 bypassPermissions 모드로 시작한다.
1
claude -p "refactor the auth module" --dangerously-skip-permissions
--allow-dangerously-skip-permissions: 세션을 bypassPermissions 모드로 시작하지 않는다.
대신 Shift+Tab 순환 목록에 bypassPermissions를 추가하기만 한다. 즉 다른 시작 모드와 함께 구성할 수 있다.
1
2
# plan 모드로 시작하되, 필요 시 Shift+Tab으로 bypassPermissions까지 전환 가능하게
claude --permission-mode plan --allow-dangerously-skip-permissions
모드별 최적 사용 사례
1) default
파일 읽기는 자유롭게, 파일 편집과 명령 실행은 매번 확인을 받는다.
- 처음 접하는 코드베이스에서 Claude와 함께 탐색할 때
- 프로덕션 관련 작업처럼 각 단계를 직접 확인하고 싶을 때
- 어떤 작업이 실행될지 예측이 어려운 상황
2) acceptEdits
파일 편집은 자동 수락, 셸 명령은 여전히 확인을 받는다.
- 코드 리뷰 중 Claude가 제안하는 수정을 빠르게 반영할 때
- 리팩토링처럼 편집이 주된 작업이지만 외부 명령 실행은 통제하고 싶을 때
3) plan
파일 읽기와 탐색은 가능하지만 소스 코드 편집은 불가능하다.
Bash 명령이나 네트워크 요청은 여전히 승인이 필요하다.
- 대규모 리팩토링 전에 Claude가 먼저 계획을 작성하게 할 때
- 방향이 잡히기 전에 변경 사항이 생기는 것을 원하지 않을 때
/plan접두어를 프롬프트에 붙여 단일 요청에만 적용할 수도 있다
4) auto
백그라운드 분류기가 각 작업을 실시간으로 검토한다. 안전하다고 판단되면 자동 실행, 위험하다고 판단되면 차단한다.
Team 플랜과 Claude Sonnet 4.6 또는 Opus 4.6 이상이 필요하다.
- 장시간 실행되는 리팩토링이나 마이그레이션 작업
- 매번 프롬프트를 확인하는 피로를 줄이면서도 안전망은 유지하고 싶을 때
- 파이프라인 자동화 중 완전한 무감시는 불안할 때
5) dontAsk
사전에 allow 규칙에 정의된 도구만 실행되고 나머지는 자동 거부된다.
- CI 파이프라인처럼 허용할 작업 목록이 명확히 고정되어 있을 때
- Claude가 실행할 수 있는 작업의 범위를 코드로 완전히 통제하고 싶을 때
1
2
3
4
5
{
"permissions": {
"allow": ["Bash(npm test)", "Bash(npm run lint)", "Read(*)", "Write(src/**)"]
}
}
위 설정과 함께 dontAsk 모드를 켜면 허용된 명령 외에는 아무것도 실행되지 않는다.
6) bypassPermissions
모든 검사가 비활성화된다. 격리된 환경 전용이다.
- 인터넷 접근이 없는 컨테이너나 VM 안에서 자동화된 작업을 실행할 때
- 호스트 시스템에 영향을 줄 수 없음이 인프라 수준에서 보장된 환경
1
claude -p "run the full test suite and fix all failures" --dangerously-skip-permissions
자동 모드(auto)와 분류기(classifier)
다른 모드와의 차별점
bypassPermissions는 “모든 것을 허용”하고, default는 “모든 것을 사람이 확인”한다.
auto는 그 사이에 있다 — 분류기라는 별도 AI 모델이 사람 대신 각 작업을 검토한다.
분류기는 주 세션과 독립적으로 Claude Sonnet 4.6에서 실행된다.
주 세션이 다른 모델을 쓰더라도 분류기는 항상 Sonnet 4.6이다.
분류기가 하는 일
각 작업이 실행되기 직전에 대화 이력과 보류 중인 작업을 받아 작업이 사용자가 요청한 범위 안에 있는지 판단한다.
분류기가 받는 입력은 사용자 메시지와 도구 호출뿐이다. Claude의 자체 텍스트와 도구 실행 결과는 포함되지 않는다.
덕분에 파일이나 웹 페이지의 악의적인 콘텐츠가 분류기를 직접 조작할 수 없다.
CLAUDE.md 내용도 분류기에 전달된다. 프로젝트 지침에 설명된 작업이 허용/차단 결정에 고려된다는 의미다.
분류기가 평가하는 기준:
- 과도한 확대(Scope escalation): 작업 범위를 초과하는 행동인가
- 신뢰할 수 없는 인프라: 분류기가 신뢰할 수 있다고 인식하지 못하는 외부 엔드포인트를 대상으로 하는가
- 의도 이탈: 파일이나 웹 콘텐츠에 의해 조종된 것으로 보이는 갑작스러운 방향 전환인가
권한 규칙이 도구 이름과 인수 패턴을 구문적으로 매칭한다면, 분류기는 컨텍스트를 이해하고 의미적으로 판단한다.
기본적으로 차단되는 것들
분류기는 작업 디렉터리와 git 리포지토리에 있는 원격은 신뢰한다. 그 외는 전부 외부로 취급한다.
기본 차단:
curl | bash또는 외부 스크립트 실행- 외부 엔드포인트로 민감한 데이터 전송
- 프로덕션 배포, 마이그레이션
- 클라우드 스토리지 대량 삭제
- IAM 또는 리포지토리 권한 수정
- 강제 푸시,
main에 직접 푸시
기본 허용:
- 작업 디렉터리의 로컬 파일 작업
- 잠금 파일에 이미 선언된 의존성 설치
- 읽기 전용 HTTP 요청
- Claude가 직접 만든 브랜치로 푸시
전체 기본 규칙 목록은 claude auto-mode defaults로 확인할 수 있다.
auto 모드 진입 시 자동 삭제되는 허용 규칙
auto 모드에 진입하면 Claude Code는 임의 코드 실행을 부여하는 것으로 알려진 허용 규칙을 일시적으로 삭제한다.
Bash(*)같은 무제한 셸 액세스Bash(python*),Bash(node*)같은 와일드카드 스크립트 인터프리터- 패키지 관리자 실행 명령
- 모든
Agent허용 규칙
Bash(npm test) 같은 좁은 규칙은 유지된다. 삭제된 규칙은 auto 모드를 종료하면 복원된다.
서브에이전트 처리
Claude가 서브에이전트를 생성할 때 분류기는 위임된 작업을 생성 시점에 평가한다.
위험해 보이는 작업 설명은 서브에이전트가 시작되기 전에 차단된다.
서브에이전트 내에서도 부모 세션과 동일한 차단/허용 규칙이 적용된다.
서브에이전트 자체 frontmatter에 정의된 permissionMode는 무시된다.
서브에이전트가 완료되면 분류기가 전체 작업 기록을 다시 검토한다.
실행 중 악의적인 콘텐츠에 의해 조종된 흔적이 있으면 보안 경고를 결과 앞에 붙여 주 에이전트가 판단할 수 있게 한다.
폴백(fallback) 동작
분류기가 한 세션에서 연속 3회 또는 총 20회 차단하면 auto 모드가 일시 중지되고 각 작업에 대해 다시 프롬프트를 표시한다.
- CLI: 상태 영역에 알림이 표시된다. 프롬프트된 작업을 승인하면 거부 카운터가 리셋되어
auto모드를 재개할 수 있다. - 비대화형 모드(
-p): 프롬프트할 사용자가 없으므로 세션을 중단한다.
연속 차단이 발생하는 원인은 보통 두 가지다.
작업이 실제로 분류기가 막도록 설계된 작업을 요구하거나, 분류기가 신뢰할 수 있는 인프라에 대한 컨텍스트가 부족해서 안전한 작업을 위험으로 판단하는 경우다.
후자라면 관리자가 autoMode.environment 설정으로 신뢰 가능한 리포지토리, 버킷, 내부 서비스를 등록해야 한다.
설정 파일로 모드 고정하기
CLI 플래그는 단발성이다. defaultMode를 settings.json에 설정하면 별도의 플래그 없이도 항상 해당 모드로 시작한다.
1
2
3
4
5
{
"permissions": {
"defaultMode": "acceptEdits"
}
}
dontAsk와 bypassPermissions도 동일하게 설정 가능하다.
1
2
3
4
5
{
"permissions": {
"defaultMode": "dontAsk"
}
}
설정 파일 위치와 적용 범위
| 파일 경로 | 적용 범위 |
|---|---|
~/.claude/settings.json | 모든 프로젝트 (사용자 전체) |
.claude/settings.json | 현재 프로젝트 (git에 포함 가능) |
.claude/settings.local.json | 현재 프로젝트 로컬 전용 (git 제외) |
bypassPermissions처럼 위험한 모드는 .claude/settings.local.json에 두는 것이 맞다.
settings.json에 넣으면 레포를 공유하는 팀원 전체에 적용된다.
bypassPermissions는 관리자가 다음 설정으로 조직 전체에서 비활성화할 수 있다.
1
2
3
4
5
{
"permissions": {
"disableBypassPermissionsMode": "disable"
}
}
권한 접근 방식 비교
default | acceptEdits | plan | auto | dontAsk | bypassPermissions | |
|---|---|---|---|---|---|---|
| 파일 읽기 | 자동 | 자동 | 자동 | 자동 | allow 규칙에 있으면 | 자동 |
| 파일 편집 | 확인 필요 | 자동 | 불가 | 자동 (분류기 통과 시) | allow 규칙에 있으면 | 자동 |
| 셸 명령 | 확인 필요 | 확인 필요 | 확인 필요 | 분류기 통과 시 자동 | allow 규칙에 있으면 | 자동 |
| 미허용 작업 | 사용자에게 물음 | 사용자에게 물음 | 사용자에게 물음 | 분류기가 차단 | 자동 거부 | 제한 없음 |
| 안전 검사 | 사람이 직접 | 명령만 사람이 | 사람이 직접 | 분류기 AI | 규칙 기반 | 없음 |
| 토큰 비용 | 표준 | 표준 | 표준 | 더 높음 (분류기 호출) | 표준 | 표준 |
| 비대화형 적합성 | 낮음 | 보통 | 낮음 | 높음 | 높음 | 높음 |
| 필요 조건 | 없음 | 없음 | 없음 | Team 플랜 + Sonnet/Opus 4.6 | 없음 | 격리 환경 필수 |
plan 모드는 승인 방식보다 “무엇을 할 수 있는가”를 제한하는 모드라 성격이 다르다.
파일 편집 자체를 막는다는 점에서 다른 모드와 구분된다.
Hooks 핵심 개념
Hooks는 Claude Code 라이프사이클의 특정 시점에 자동으로 실행되는 커스텀 셸 명령, HTTP 엔드포인트, 또는 LLM 프롬프트다.
권한 규칙이 도구 이름/인수 패턴 매칭으로 표현할 수 없는 로직을 구현할 때 사용한다.
주요 이벤트 종류
| 이벤트 | 실행 시점 | 차단 가능 여부 |
|---|---|---|
PreToolUse | 도구 실행 직전 | 가능 |
PostToolUse | 도구 실행 성공 후 | 가능 (재실행 요청) |
PermissionRequest | 권한 대화가 표시될 때 | 가능 (대화 자체를 가로챔) |
Stop | Claude가 응답을 완료했을 때 | 가능 (추가 작업 강제) |
UserPromptSubmit | Claude가 사용자 입력을 처리하기 전 | 가능 |
SessionStart | 세션이 시작되거나 재개될 때 | 불가능 |
입출력 구조
Hook은 stdin으로 JSON을 받고, stdout으로 JSON을 출력하며, exit code로 동작을 제어한다.
1
2
3
4
5
6
7
8
9
// stdin으로 들어오는 공통 필드
{
"session_id": "abc123",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "rm -rf /tmp/test" },
"cwd": "/project",
"permission_mode": "default"
}
exit code 의미:
| exit code | 의미 | 동작 |
|---|---|---|
0 | 성공 | stdout JSON을 파싱하여 결정 적용 |
2 | 차단 오류 | stderr 내용을 Claude에게 전달하고 실행 차단 |
| 기타 | 비차단 오류 | 오류 로그만 남기고 실행 계속 |
설정 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/project/.claude/hooks/validate.sh"
}
]
}
]
}
}
3단계 구조: 이벤트 → 매처(필터) → 핸들러.
매처는 정규식이다. PreToolUse에서는 도구 이름에 매칭된다.
매처를 생략하거나 "*"를 쓰면 모든 경우에 실행된다.
PreToolUse에서 차단하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command' < /dev/stdin)
if echo "$COMMAND" | grep -qE 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "rm -rf 는 허용되지 않습니다"
}
}'
exit 0
fi
exit 0
permissionDecision은 "allow", "deny", "ask" 중 하나다.
Stop 이벤트로 추가 작업 강제하기
Claude가 응답을 완료한 시점에 실행된다.
"decision": "block"을 반환하면 Claude에게 추가 작업을 수행하게 할 수 있다.
1
2
3
4
{
"decision": "block",
"reason": "테스트를 먼저 실행해야 합니다"
}
핸들러 타입
command (셸 명령), http (외부 서버에 POST), prompt (LLM에게 판단 위임), agent (서브에이전트에 위임) 4가지다.
1
2
3
4
5
6
7
// HTTP 핸들러 예시: 외부 정책 서버에 검증 위임
{
"type": "http",
"url": "http://policy-server/validate",
"headers": { "Authorization": "Bearer $TOKEN" },
"allowedEnvVars": ["TOKEN"]
}
주의사항
- Hook은 사용자 권한으로 실행된다. 프로덕션에 적용하기 전에 충분히 테스트해야 한다.
- stdout에 JSON이 아닌 출력(예: shell profile의 환경 메시지)이 섞이면 JSON 파싱이 깨진다.
async: true로 설정한 비동기 Hook은 차단 기능을 사용할 수 없다./hooks명령으로 현재 설정된 Hook 목록을 확인할 수 있다.