
Claude Code의 settings.json 우선순위 실측 기록: permissions는 '덮어쓰기'가 아닌 '병합'
요약
본 기사는 Claude Code의 설정 파일 우선순위와 `permissions` 동작 방식을 직접 실측한 기록입니다. 일반적인 설정은 상위 스코프가 우선하지만, `permissions`는 '덮어쓰기(overwrite)'가 아닌 '병합(merge)'된다는 것이 핵심입니다. 또한, 환경 변수 및 훅을 이용해 신뢰성 높은 검증 방법을 제시합니다.
핵심 포인트
- Claude Code의 설정은 상위 스코프 우선순위를 따르나, `permissions`는 병합됩니다.
- 환경 변수와 `SessionStart` 훅 조합으로 모델 응답에 의존하지 않는 정확한 테스트가 가능합니다.
- `--settings` 훅은 `--setting-sources` 대상에서 제외되어 항상 작동함을 확인했습니다.
- 권한 충돌 시 평가 순서는 'deny' -> 'ask' -> 'allow' 순서로 진행됩니다.
settings.local.json에 작성했는데 적용되지 않는다. 우선순위에 대해 설명한 글은 많지만, 순서가 반대로 적혀 있는 경우도 있다. 게다가 permissions는 다른 설정과 동작 방식이 달라서 '덮어쓰기(overwrite)'가 아니라 '병합(merge)'된다. 이 부분을 놓치면 문제 해결 방향을 완전히 잘못 잡게 된다.
본 기사는 우선순위와 permissions의 동작 방식을 직접 실측한 기록이다. 추측은 적지 않는다. 확인하지 못한 것은 명시한다.
검증 환경
$ claude --version
2.1.221 (Claude Code)
$ claude doctor
...
검증은 임시 디렉터리의 일회용 프로젝트에서 진행했으며, ~/.claude/settings.json는 전혀 수정하지 않았다.
측정 방법: 모델에게 묻지 않고, 훅(hook)으로 관측하기
'어떤 스코프가 승리했는지'를 Claude에게 물어보는 것은 신뢰할 수 없다. 실제로 검증 중에 권한 때문에 거부된 명령어에 대해 Claude가 'DONE'이라고 답한 경우가 있었다 (훅과 파일의 실체를 보면 실행되지 않았다). 모델의 응답을 성공 판정에 사용해서는 안 된다.
그래서 env 설정과 SessionStart 훅을 조합했다. env는 세션과 거기서 시작되는 서브 프로세스에 환경 변수를 적용하고, 훅은 그 서브 프로세스로 동작한다. 각 스코프에 모순되는 env를 작성하면, 실효값이 그대로 로그에 남는다.
{
"hooks": {
"SessionStart": [
...
각 스코프에는 값만 넣었다. user는 {"env":{"PRIORITY_PROBE":"USER","OTHER_PROBE":"only-in-user"}}이고, project는 `
는 살아남았다. 3. --setting-sources는 읽어올 스코프(scope)를 좁힐 수 있다.
''
을 전달하면 user/project/local의 모든 값이 사라졌다. 반면 --settings 훅은 이 경우에도 작동했으므로, --settings는 --setting-sources의 대상에서 제외된다.
실측 2: permissions는 병합(merge)된다
이 부분이 핵심이다. 다른 설정은 상위 스코프가 우선하지만, permissions는 다르다.
Permission rules behave differently because they merge across scopes rather than override. (Settings)
project와 local에 서로 다른 allow를 작성하여 실측했다.
// .claude/settings.json
{ "permissions": { "allow": ["Bash(touch p_marker)"] } }
// .claude/settings.local.json
...
두 명령어를 모두 실행하게 한 결과, 둘 다 허용되어 실행되었다 (PreToolUse / PostToolUse가 각각 2회씩 발생했으며, 파일도 2개가 생성됨). 만약 local이 project를 덮어썼다면 p_marker는 허용되지 않았어야 한다. allow 리스트는 스코프를 가로질러 결합된다.
실무적인 의미는 크다. settings.local.json에 개인용 allow를 추가해도 리포지토리 측의 allow는 사라지지 않는다. 반대로, 팀 설정에 위험한 allow가 있더라도 local에서 이를 상쇄할 수 없으며, 상쇄하려면 deny가 필요하다.
실측 3: deny와 allow가 충돌할 때
평가 순서는 공식적으로 명시되어 있다.
Rules are evaluated in order: deny, then ask, then allow. The first match in that order determines the outcome, and rule specificity doesn't change the order. (Configure permissions)
"구체성이 순서를 바꾸지 않는다"가 포인트다. 실행 여부는 PostToolUse의 발생과 파일 생성을 통해 판정했다.
| 설정한 조합 | 결과 |
|---|---|
user에 allow Bash(git add *)만 설정 | 실행됨 |
| user allow + project deny (동일 패턴) | 거부 (denied by permission settings) |
project deny Bash(touch *) + local allow 동일 | 거부 (상위 스코프의 allow로도 이길 수 없음) |
project allow Bash(touch *) + local deny 동일 | 거부 |
local allow Bash(touch *) + CLI --disallowedTools 동일 | 거부 |
project ask Bash(touch *) + local allow 동일 | 승인 대기 중이며 미실행 |
어느 스코프의 deny든, 어느 스코프의 allow보다 우선한다. 우선순위의 상하 관계와는 무관하다. ask 또한 allow보다 앞서기 때문에, local에서 allow를 했더라도 project에 ask가 있다면 확인을 요청받게 된다.
첫 번째 행은 "user는 최하위니까 무시된다"는 것이 아님을 확인한 것이다. 누구도 부정하지 않는다면 user의 allow도 정상적으로 작동한다 (신뢰할 수 없는 임시 디렉토리에서 실행했으므로, user 스코프가 신뢰 승인을 요구하지 않는다는 점도 동시에 확인되었다). 반대 방향(user deny × project allow)은 실제 ~/.claude/settings.json을 수정하지 않고는 재현할 방법이 없어 실측하지 않았다. 공식 문서에는 "a user-level deny blocks a project-level allow"라고 명시되어 있지만, 직접 눈으로 확인하지는 못했다.
"설정했는데 적용되지 않음"의 분류 절차
우선순위를 보기 전에 먼저 확인해야 할 함정이 두 가지 있다.
단계 1: 해당 파일이 읽히고 있는가
git 리포지토리의 루트에 설정을 두고, 서브 디렉토리에서 실행하여 측정했다.
| 실행 위치 | 루트의 settings.json |
|---|---|
| 루트 (Repository root) | 읽힘 |
서브 디렉토리 (sub/.claude 없음) | 읽히지 않음 |
서브 디렉토리 (sub/.claude/settings.json 있음) | sub/ 쪽이 읽힘 |
.claude/settings.json은 상위 디렉토리로 거슬러 올라가지 않는다. 현재 디렉토리의 .claude/만 확인한다. 반면 settings.local.json은 git 리포지토리의 루트에서 읽히며, 서브 디렉토리 실행 시에도 적용되었다 (v2.1.211 이후의 동작). 모노레포(Monorepo)의 packages/foo/에서 "직하에 작성한 설정이 적용되지 않는다"면 우선 이것을 의심해야 한다.
단계 2: 워크스페이스 신뢰를 승인했는가
"allow만 적용되지 않을" 때의 범인은 거의 이것이다. 신뢰되지 않은 디렉토리에서 실제로 발생한 경고는 다음과 같다.
Ignoring 1 permissions.allow entry from .claude/settings.json: this workspace has
not been trusted. Run Claude Code interactively here once and accept the trust
dialog, or set projects["/path/to/workspace"].hasTrustDialogAccepted: true in
...
프로젝트의 settings.json에 있는 permissions.allow와 additionalDirectories는 권한을 부여하는 설정이므로, 신뢰 대화 상자(Trust dialog)를 승인하기 전까지는 적용되지 않는다. 파일은 읽히고 있지만 적용되지 않는 것이다. 반면 deny와 ask는 제한만 하는 설정이므로 영향을 받지 않는다.
동일한 검증에서, settings.local.json 측의 allow에는 이 경고가 나타나지 않았으며 실제로 적용되고 있었다. 사용자가 직접 만든 파일이므로 리포지토리 제공(Repository-supplied)이 아니라고 판단되기 때문이다 (git에 커밋되어 있는 경우에는 신뢰가 필요하다).
즉, "project의 allow는 작동하지 않는데 local의 allow는 작동한다"면, 원인은 우선순위가 아니라 신뢰 승인 누락이다. 대화형 세션을 해당 위치에서 한 번 실행하여 승인한다. CI와 같이 대화형 사용이 불가능한 환경에서는 --settings로 전달하거나 user 스코프에 둔다. -p (print) 모드에서는 신뢰 대화 상자 자체가 스킵되므로, CI에서 allow가 작동하지 않는 원인은 이와 직결된다.
단계 3·4: deny/ask와의 충돌 → 마지막으로 우선순위
여기까지 확인해도 적용되지 않는다면, 어딘가에 있는 deny / ask에 먼저 매칭되고 있을 가능성을 살펴봐야 한다. 우선순위로는 뒤집을 수 없으므로, allow를 추가하는 것이 아니라 해당 규칙을 찾아 삭제해야 한다. 그 단계에 이르러서야 비로소 우선순위 이야기가 되며, settings.local.json의 오래된 값이 settings.json의 새로운 값을 덮어쓰고 있지는 않은지 확인한다.
실효값을 확인하는 방법
**/permissions (대화형 세션 내)**를 사용하는 것이 정석이다. 공식 문서에 따르면 모든 규칙의 목록과 각 규칙의 출처 파일을 표시한다. 단, -p 모드에서는 사용할 수 없다 (시도하면 /permissions isn't available in this environment.라는 응답이 돌아온다). 실행 시의 stderr 경고는 경시되기 쉽지만, 위와 같이 원인과 대처법을 구체적으로 제시한다. 파이프로 버리고 있으면 알아차릴 수 없다.
claude doctor는 실효 설정 확인용으로 사용할 수 없다. 실제로 실행해 보았으나, 출력 내용은 설치 방법, 버전, 자동 업데이트, 인증 상태의 건전성 체크이며, settings의 값이나 permissions 규칙은 표시되지 않는다.
확실하게 보고 싶다면 본 기사의 관측 방법이 빠르다. 후크(Hook)는 모델을 거치지 않는다.
PROBE_LOG=/tmp/probe.log claude -p 'x' --settings ./probe.json
cat /tmp/probe.log
확인하지 않은 사항
- managed (관리자 스코프)는 실측하지 않음. macOS에서는
/Library/Application Support/ClaudeCode/에 대한 쓰기 권한이 필요하며, 검증 기기에 해당 디렉토리가 존재하지 않고, root 권한을 통한 생성은 검증 범위를 벗어나므로 수행하지 않았다. 공식 문서에는 "can't be overridden by anything (명령줄 인수를 포함하여)"라고 명시되어 있다. - user deny × project allow의 방향성은 실측하지 않음.
- 검증은 macOS (darwin-arm64) / v2.1.221 만을 대상으로 함. Linux 및 Windows는 미확인 상태이며, 동작은 버전에 따라 달라질 수 있음.
요약
- 우선순위는 managed > 명령줄 (command line) > local > project > user 순이며, user가 최하위임.
- 단,
permissions는 이 우선순위에 따라 덮어씌워지지 않고, 스코프(scope)를 가로질러 병합(merge)됨. deny와ask는 allow보다 먼저 평가되며, 어느 스코프의 deny라도 어느 스코프의 allow보다 우선함.- 판별 순서는 ① 파일이 읽히고 있는가 → ② 신뢰 승인 → ③ deny/ask와의 충돌 → ④ 우선순위 순임.
관련 기사
- Claude Code의 서브 에이전트, 결국 언제 사용해야 하는가 — 서브 에이전트의
permissionMode는 부모 대화의 권한 모드에 의해 덮어씌워짐. 본 기사의 스코프 우선순위와는 별개의 계통인 "덮어쓰기"이므로 혼동하지 않도록 정리해 두는 것이 좋음. - 「.claude/agents」 설계에서 저지르기 쉬운 실수 5가지 — 허가 리스트(allow list)와 제외 리스트(deny list)가 동일한 안전성을 갖지 않는다는 본 기사의 deny 우선 원칙과 맥락을 같이 하는 내용.
설정 템플릿으로 해결하고 싶은 경우
permissions가 병합된다면, 리포지토리의 .claude/settings.json에는 팀원 전체에게 적용하고 싶은 최소한의 하한선만 작성하고, 개인의 취향은 user나 local에 두는 것이 가장 깔끔한 구분 방식임.
Claude Code 설정 템플릿 모음(BOOTH · 2,980엔)에는 이러한 구분 방식을 전제로 **용도별 settings.json 3종 (개인 / 팀 공유 / CI)**을 수록하고 있음. 각 파일 서두에 "왜 이 스코프에 이것을 작성하는가"를 주석으로 달아두었으므로, 3종을 비교해 보면 역할 분담에 대한 개념을 파악할 수 있음. 추가로 **실패 진단표 (118개 항목)**에는 "settings.json이 적용되지 않음" 섹션이 있어, 본 기사의 단계 1·2에 해당하는 증상(신뢰 승인 경고 문구, hooks의 재시작 필요 여부 등)을 증상 → 원인 → 대처 순으로 찾아볼 수 있음.
본 기사는 공식 문서(Settings / Configure permissions)를 확보하여 근거를 확인하는 동시에, Claude Code v2.1.221을 임시 디렉토리에서 실제로 구동하여 검증하였으며, AI 어시스턴트(Claude)의 지원을 받아 작성되었다. 기사 중의 표는 모두 해당 실측 결과이며, 실측하지 않은 범위는 명시하였다. 동작은 버전에 따라 달라질 수 있으므로, 중요한 설정은 반드시 자신의 환경에서 확인하기 바란다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기