설정 & 구성

5계층 캐스케이드, 3단계 캐시, 변경 감지, MDM, 원격 관리 설정.

01

개요

Claude Code의 설정 시스템은 개인 개발자의 로컬 환경설정부터 엔터프라이즈 정책까지 다양한 소스의 설정을 일관된 인터페이스로 병합합니다. 5계층 캐스케이드, 3단계 캐시 아키텍처, chokidar 기반 변경 감지, MDM 및 원격 관리 설정을 통해 대규모 조직에서도 안전하고 효율적인 설정 관리를 제공합니다.

다루는 소스 파일: settings/ 디렉토리 — settingsManager.ts, settingsSources.ts, settingsCache.ts, settingsWatcher.ts, mdmSettings.ts, remoteSettings.ts
02

5계층 캐스케이드

설정은 5개 계층에서 로드되며, SETTING_SOURCES 배열에서 나중에 오는 소스가 이전 소스를 덮어씁니다:

  1. userSettings~/.claude/settings.json (사용자 전역 설정)
  2. projectSettings.claude/settings.json (프로젝트 범위, git 추적)
  3. localSettings.claude/settings.local.json (로컬 전용, gitignore)
  4. CLI 플래그 — 명령줄 인수 (--model, --allowedTools 등)
  5. policySettings — 정책 설정 (가장 높은 우선순위)
// settingsSources.ts — 소스 순서 (나중이 우선)
const SETTING_SOURCES = [
  'userSettings',
  'projectSettings',
  'localSettings',
  'flagSettings',
  'policySettings',
] as const

policySettings 내부 우선순위

policySettings 계층 내부에서도 4개 소스가 존재하며, first-source-wins 방식으로 평가됩니다:

  1. remote — 원격 관리 설정 (최우선)
  2. MDM — macOS plist / Windows 레지스트리
  3. filemanaged-settings.json 파일
  4. HKCU — Windows 사용자 레지스트리
주의사항

projectSettings에서 보안에 민감한 플래그를 설정하면 무시됩니다. 예를 들어 skipDangerousModePermissionPrompt: trueprojectSettings의 신뢰 소스에서 제외되어 있습니다 — 악의적인 리포지토리가 이 플래그를 설정하면 원격 코드 실행(RCE) 위험이 발생하기 때문입니다.

03

병합 의미론

설정 값의 타입에 따라 병합 전략이 다릅니다:

// settingsManager.ts — 배열 병합: 중복 제거 연결
// userSettings.allow = ["Read(**)", "Bash(git *)"]
// projectSettings.allow = ["Bash(git *)", "Write(src/)"]
// 병합 결과: ["Read(**)", "Bash(git *)", "Write(src/)"]

function mergeArrays(a: string[], b: string[]): string[] {
  return [...new Set([...a, ...b])]
}

위 예시에서 userSettings["Read(**)", "Bash(git *)"]projectSettings["Bash(git *)", "Write(src/)"]가 중복 제거 연결되어 ["Read(**)", "Bash(git *)", "Write(src/)"]가 됩니다. 중복된 "Bash(git *)"는 한 번만 포함됩니다.

04

3단계 캐시

설정 읽기 성능을 최적화하기 위해 3단계 캐시 아키텍처를 사용합니다:

딥 다이브 — 캐시 무효화 전파

캐시 무효화는 하위에서 상위로 전파됩니다. 파일이 변경되면 Parse-File 캐시가 무효화되고, 해당 소스의 Per-Source 캐시가 무효화되며, 최종적으로 Session 캐시가 재계산됩니다. 이 계층적 무효화 덕분에 대부분의 설정 읽기는 Session 캐시에서 즉시 반환됩니다.

05

변경 감지

설정 파일 변경은 두 가지 메커니즘으로 감지됩니다:

내부 쓰기 억제 (markInternalWrite)

Claude Code가 자체적으로 설정 파일을 수정할 때(예: 마이그레이션), 변경 알림이 불필요한 리로드를 트리거하지 않도록 억제합니다:

// settingsWatcher.ts — 내부 쓰기 시 5초간 이벤트 억제
function markInternalWrite(): void {
  internalWriteTimestamp = Date.now()
}

function onFileChange(path: string): void {
  if (Date.now() - internalWriteTimestamp < 5000) {
    return // 5초 윈도우 내 변경 무시
  }
  invalidateCache(path)
}

markInternalWrite()를 호출하면 이후 5초 이내의 chokidar 이벤트가 억제됩니다. 이 윈도우는 파일 쓰기와 파일시스템 이벤트 전파 사이의 타이밍 차이를 커버하기에 충분합니다.

06

MDM & 원격 관리 설정

MDM (모바일 디바이스 관리)

엔터프라이즈 환경에서는 macOS plist 또는 Windows 레지스트리를 통해 정책을 배포합니다. Claude Code는 plutil(macOS) 또는 reg query(Windows) 서브프로세스를 실행하여 MDM 설정을 읽습니다.

파일 기반 관리 설정

managed-settings.json 파일은 MDM을 사용할 수 없는 환경에서 정책을 배포하는 대안입니다. MDM과 파일 모두 존재할 경우, policySettings 내 first-source-wins 규칙에 따라 MDM이 파일보다 우선합니다.

원격 관리 설정

원격(remote) 관리 설정은 HTTP 엔드포인트에서 가져오며, ETag 기반 캐싱으로 네트워크 비용을 최소화합니다. 서버 응답이 304 Not Modified이면 캐시된 값을 사용합니다.

설정 동기화 (CCR)

CCR(Claude Code Remote) 환경에서는 로컬과 원격 설정을 동기화하여 일관된 동작을 보장합니다.

07

핵심 요약

핵심 포인트

  • 설정은 5계층 캐스케이드(user → project → local → flag → policy)로 병합되며, SETTING_SOURCES에서 나중에 오는 소스가 우선합니다
  • policySettings 내부에서도 remote → MDM → file → HKCU 순서의 first-source-wins 우선순위가 적용됩니다
  • 병합 전략은 타입별로 다릅니다: 객체는 딥 병합, 배열은 중복 제거 연결, 스칼라는 덮어쓰기
  • 3단계 캐시(Session / Per-Source / Parse-File)가 설정 읽기 성능을 최적화합니다
  • markInternalWrite()는 Claude Code 자체 쓰기 시 5초간 chokidar 이벤트를 억제하여 불필요한 리로드를 방지합니다
  • projectSettings에서 보안에 민감한 플래그(skipDangerousModePermissionPrompt 등)는 무시됩니다 — RCE 방지
  • MDM 설정은 30분 간격으로 폴링되며, 원격 설정은 ETag 캐싱을 사용합니다
08

지식 확인

퀴즈 — 5문제

Q1. userSettingsprojectSettings에 같은 스칼라 설정이 있을 때 어느 것이 적용되나요?

  • A) userSettings — 사용자 설정이 항상 우선
  • B) projectSettingsSETTING_SOURCES에서 나중에 오므로 덮어쓰기
  • C) 충돌 오류 발생
  • D) userSettings가 항상 우선하도록 하드코딩
SETTING_SOURCES 배열에서 projectSettingsuserSettings 뒤에 위치합니다. 스칼라 값은 나중 소스가 이전 소스를 덮어쓰므로, projectSettings의 값이 적용됩니다.

Q2. macOS MDM plist와 managed-settings.json 파일이 모두 존재할 때 어느 것이 우선하나요?

  • A) 파일 — managed-settings.json이 우선
  • B) MDM — policySettings 내 first-source-wins에서 MDM이 file보다 높은 우선순위
  • C) 두 소스가 병합됨
  • D) remote 설정이 있어야 결정 가능
policySettings 내부 우선순위는 remote → MDM → file → HKCU입니다. First-source-wins 방식에서 MDM이 file보다 먼저 평가되므로, MDM 설정이 있으면 file의 같은 키는 무시됩니다.

Q3. Claude Code가 자체적으로 설정 파일을 쓸 때 어떻게 처리하나요?

  • A) 일반 변경 알림이 발생하여 리로드됨
  • B) markInternalWrite()를 호출하여 chokidar 이벤트를 5초 내 억제
  • C) 파일 감시기를 일시적으로 비활성화
  • D) 임시 파일에 쓴 후 원자적 이름 변경
markInternalWrite()는 내부 쓰기 타임스탬프를 기록하고, 이후 5초 이내에 발생하는 chokidar 파일 변경 이벤트를 무시합니다. 감시기 자체를 비활성화하지 않으므로 외부 변경은 5초 후 정상 감지됩니다.

Q4. projectSettingsskipDangerousModePermissionPrompt: true를 설정하면?

  • A) 설정이 존중되어 프롬프트 건너뜀
  • B) 무시됨 — projectSettings는 이 플래그의 신뢰 소스에서 제외 (RCE 위험)
  • C) localSettings에도 설정해야 적용됨
  • D) CLI 플래그로만 설정 가능
보안에 민감한 플래그는 projectSettings에서 의도적으로 제외됩니다. 악의적인 리포지토리가 .claude/settings.json에 이 플래그를 설정하면 클론한 사용자의 권한 보호를 무력화할 수 있어 원격 코드 실행(RCE) 취약점이 됩니다.

Q5. userSettingsallow: ["Read(**)", "Bash(git *)"], projectSettingsallow: ["Bash(git *)", "Write(src/)"] 설정 시 병합 결과는?

  • A) projectSettings가 덮어쓰기: ["Bash(git *)", "Write(src/)"]
  • B) 중복 제거 연결: ["Read(**)", "Bash(git *)", "Write(src/)"]
  • C) 중복 포함 연결: ["Read(**)", "Bash(git *)", "Bash(git *)", "Write(src/)"]
  • D) userSettings만 유지: ["Read(**)", "Bash(git *)"]
배열 타입 설정은 중복 제거 연결 전략을 사용합니다. 두 배열을 연결한 후 중복 항목을 제거하므로, "Bash(git *)"는 한 번만 포함됩니다. 결과: ["Read(**)", "Bash(git *)", "Write(src/)"]
0 / 5