5계층 캐스케이드, 3단계 캐시, 변경 감지, MDM, 원격 관리 설정.
Claude Code의 설정 시스템은 개인 개발자의 로컬 환경설정부터 엔터프라이즈 정책까지 다양한 소스의 설정을 일관된 인터페이스로 병합합니다. 5계층 캐스케이드, 3단계 캐시 아키텍처, chokidar 기반 변경 감지, MDM 및 원격 관리 설정을 통해 대규모 조직에서도 안전하고 효율적인 설정 관리를 제공합니다.
settings/ 디렉토리 — settingsManager.ts, settingsSources.ts, settingsCache.ts, settingsWatcher.ts, mdmSettings.ts, remoteSettings.ts
설정은 5개 계층에서 로드되며, SETTING_SOURCES 배열에서 나중에 오는 소스가 이전 소스를 덮어씁니다:
~/.claude/settings.json (사용자 전역 설정).claude/settings.json (프로젝트 범위, git 추적).claude/settings.local.json (로컬 전용, gitignore)--model, --allowedTools 등)// settingsSources.ts — 소스 순서 (나중이 우선)
const SETTING_SOURCES = [
'userSettings',
'projectSettings',
'localSettings',
'flagSettings',
'policySettings',
] as const
policySettings 내부 우선순위policySettings 계층 내부에서도 4개 소스가 존재하며, first-source-wins 방식으로 평가됩니다:
managed-settings.json 파일projectSettings에서 보안에 민감한 플래그를 설정하면 무시됩니다. 예를 들어 skipDangerousModePermissionPrompt: true는 projectSettings의 신뢰 소스에서 제외되어 있습니다 — 악의적인 리포지토리가 이 플래그를 설정하면 원격 코드 실행(RCE) 위험이 발생하기 때문입니다.
설정 값의 타입에 따라 병합 전략이 다릅니다:
// 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 *)"는 한 번만 포함됩니다.
설정 읽기 성능을 최적화하기 위해 3단계 캐시 아키텍처를 사용합니다:
캐시 무효화는 하위에서 상위로 전파됩니다. 파일이 변경되면 Parse-File 캐시가 무효화되고, 해당 소스의 Per-Source 캐시가 무효화되며, 최종적으로 Session 캐시가 재계산됩니다. 이 계층적 무효화 덕분에 대부분의 설정 읽기는 Session 캐시에서 즉시 반환됩니다.
설정 파일 변경은 두 가지 메커니즘으로 감지됩니다:
settings.json, settings.local.json)의 실시간 변경 감지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 이벤트가 억제됩니다. 이 윈도우는 파일 쓰기와 파일시스템 이벤트 전파 사이의 타이밍 차이를 커버하기에 충분합니다.
엔터프라이즈 환경에서는 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(Claude Code Remote) 환경에서는 로컬과 원격 설정을 동기화하여 일관된 동작을 보장합니다.
SETTING_SOURCES에서 나중에 오는 소스가 우선합니다policySettings 내부에서도 remote → MDM → file → HKCU 순서의 first-source-wins 우선순위가 적용됩니다markInternalWrite()는 Claude Code 자체 쓰기 시 5초간 chokidar 이벤트를 억제하여 불필요한 리로드를 방지합니다projectSettings에서 보안에 민감한 플래그(skipDangerousModePermissionPrompt 등)는 무시됩니다 — RCE 방지Q1. userSettings와 projectSettings에 같은 스칼라 설정이 있을 때 어느 것이 적용되나요?
SETTING_SOURCES 배열에서 projectSettings는 userSettings 뒤에 위치합니다. 스칼라 값은 나중 소스가 이전 소스를 덮어쓰므로, projectSettings의 값이 적용됩니다.Q2. macOS MDM plist와 managed-settings.json 파일이 모두 존재할 때 어느 것이 우선하나요?
policySettings 내부 우선순위는 remote → MDM → file → HKCU입니다. First-source-wins 방식에서 MDM이 file보다 먼저 평가되므로, MDM 설정이 있으면 file의 같은 키는 무시됩니다.Q3. Claude Code가 자체적으로 설정 파일을 쓸 때 어떻게 처리하나요?
markInternalWrite()는 내부 쓰기 타임스탬프를 기록하고, 이후 5초 이내에 발생하는 chokidar 파일 변경 이벤트를 무시합니다. 감시기 자체를 비활성화하지 않으므로 외부 변경은 5초 후 정상 감지됩니다.Q4. projectSettings에 skipDangerousModePermissionPrompt: true를 설정하면?
projectSettings에서 의도적으로 제외됩니다. 악의적인 리포지토리가 .claude/settings.json에 이 플래그를 설정하면 클론한 사용자의 권한 보호를 무력화할 수 있어 원격 코드 실행(RCE) 취약점이 됩니다.Q5. userSettings에 allow: ["Read(**)", "Bash(git *)"], projectSettings에 allow: ["Bash(git *)", "Write(src/)"] 설정 시 병합 결과는?
"Bash(git *)"는 한 번만 포함됩니다. 결과: ["Read(**)", "Bash(git *)", "Write(src/)"]