플랫폼 팀은 엔지니어링 조직 전체에서 Claude Code를 활성화합니다. 도구 호출에 대한 정책을 시행하기 위해 후크 서버(stdin에서 후크 이벤트를 수신하고 stdout에서 JSON 결정을 반환하는 로컬 프로세스)를 연결합니다. 첫 주는 괜찮아 보이는데요. PreToolUse는 모든 도구 호출 전에 실행되고 후크는 프로덕션과 관련된 모든 항목에 대해 deny를 반환하며 팀은 시행이 효과가 있다고 믿습니다.
그런 다음 Claude Code는 새 릴리스를 제공합니다. PermissionRequest는 PreToolUse와 함께 발사되기 시작합니다. 후크 서버는 이미 반환했던 것과 동일한 permissionDecision JSON을 반환합니다. Claude Code는 응답을 수락하고 구문 분석한 후 해당 이벤트에 대해 인식하는 필드를 찾지 못하고 마치 결정이 내려지지 않은 것처럼 진행합니다. 거부는 자동으로 무시됩니다. 팀의 집행 지점은 이제 gap가 있는 벽이 되었으며 로그에는 그런 내용이 나와 있지 않습니다.
이것이 관리형 PEP가 해결해야 하는 문제입니다. Claude Code의 후크 수명 주기는 단일 와이어 형식의 단일 이벤트가 아닙니다. 이는 approximately 30개의 이벤트로, 각각 런타임에서 인정하는 서로 다른 출력 스키마를 가지며 스키마의 실수는 침묵과 구별할 수 없습니다.
와이어 메커니즘은 보안 계약입니다.
단일 permissionDecision 필드가 모든 곳에서 작동하지 않는 이유는 Claude Code의 후크 이벤트가 독립적으로 진화했기 때문입니다. 도구 호출을 게이트하는 출력 모양은 프롬프트 제출을 게이트하는 모양이 아니며 작업을 중지하는 모양도 아닙니다.
6가지의 서로 다른 와이어 메커니즘이 있습니다.
| 메커니즘 | 출력 형태 | 이벤트 |
|---|---|---|
permissionDecision | hookSpecificOutput.permissionDecision(허용/deny/ask/defer) | PreTool사용 |
permissionBehavior | hookSpecificOutput.decision.behavior(허용/deny) | 허가요청 |
topLevelDecision | 최상위 decision: "block" + reason | UserPromptSubmit, UserPromptExpansion, PreCompact, ConfigChange, PostToolBatch |
continueFalse | continue: false + stopReason | TaskCreated, TaskCompleted, TeammateIdle |
postToolUse | decision: "block"(추가 처리 차단, 도구가 이미 실행됨) | PostTool사용 |
neutral | 시행 가능한 차단 없음 | 모든 context/observe 이벤트 및 Stop과 같은 반전 이벤트 |
PEP가 PermissionRequest 이벤트에서 permissionDecision: "deny"를 반환하는 경우 Claude Code는 이를 무시합니다. 해당 이벤트는 permissionDecision가 아닌 decision.behavior를 예상합니다. JSON은 유효하고 HTTP 응답은 200이며 거부가 존재하지 않습니다. 이는 이론적으로 극단적인 경우는 아닙니다. 이는 code.claude.com/docs/en/hooks에 대해 검증된 문서화된 전신 계약입니다.
3방향 분류: 게이팅, 컨텍스트, 관찰
모든 후크 이벤트가 작업을 제어할 수 있는 것은 아닙니다. SessionEnd 또는 Notification를 거부하는 것은 의미가 없습니다. 이러한 이벤트는 정보 제공용이며 결정 제어 기능이 없습니다. Stop 이벤트를 거부하려고 시도하는 것은 무의미한 것보다 더 나쁩니다. Stop의 decision: "block"는 에이전트를 계속 실행하며 이는 안전 정지와 반대입니다.
PEP는 인식된 모든 후크 이벤트를 세 가지 범주 중 하나로 분류합니다.
게이팅 이벤트는 작업을 허용, 거부 또는 차단할 수 있는 결정을 내립니다. 그것들은 집행 표면입니다. 그러나 모든 게이팅 이벤트가 동일하게 적용 가능한 것은 아닙니다. Stop 및 SubagentStop는 Claude Code 자체 분류 체계에서 게이팅으로 분류되지만 해당 블록 의미 체계는 반전되어(블록 = 계속 실행) PEP는 이를 중립으로 취급합니다. 즉, 운영자의 의도에 반하여 에이전트를 활성 상태로 유지하는 블록을 생성하지 않습니다. 마찬가지로 Elicitation 및 ElicitationResult에는 게이팅 분류가 있지만 현재 버전에는 유선 작업 메커니즘이 없기 때문에 PEP는 그렇지 않은 곳에 적용이 있는 척하기보다는 중립으로 기본 설정합니다.
컨텍스트 이벤트를 통해 PEP는 additionalContext를 삽입하거나 출력을 다시 작성할 수 있지만 실제로 작업을 차단할 수는 없습니다. PostToolUse는 partial 예외입니다. 플래그가 지정된 출력의 추가 처리를 차단할 수 있지만 도구가 이미 실행되었습니다. 나머지(PermissionDenied, MessageDisplay, SessionStart, Setup, SubagentStart, PostCompact, InstructionsLoaded, PostToolUseFailure)는 컨텍스트와 함께 관찰합니다. 모델의 뷰를 풍부하게 하는 데 유용합니다. 멈추기 위해서가 아닙니다.
관찰 이벤트(Notification, SessionEnd, StopFailure, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove)에는 의사결정 제어가 전혀 없습니다. PEP는 원격 측정 경로 및 SIEM 인벤토리에 대해 이를 기록하고 중립적으로 응답한 후 계속 진행합니다.
이 분류는 권고사항이 아닙니다. 컴포지션 루트 결정자가 정책 규칙을 적용하는지(게이팅 + 시행 가능), 컨텍스트를 삽입하는지(컨텍스트), 아니면 관찰만 하는지(관찰) 여부를 결정합니다. 잘못되었다는 것은 잘못된 적용(무시된 거부 반환) 또는 적용 누락(게이트 이벤트를 관찰로 처리)을 의미합니다.
HookSpecs 맵: 단일 정보 소스
분류, 연결 메커니즘, 시행 가능성 플래그가 하나의 맵에 표시됩니다. 이는 커넥터가 사용하는 실제 코드입니다. HTTP 응답 렌더러와 여기에서 읽는 컴포지션 루트 결정자가 모두 사용합니다.
type hookSpec struct {
class string // "gating" | "context" | "observe"
mech hookMech
enforceable bool
}
var hookSpecs = map[string]hookSpec{
// GATING — 후크 반환으로 작업을 allow/deny/block할 수 있다.
"PreToolUse": {"gating", mechPermissionDecision, true},
"PermissionRequest": {"gating", mechPermissionBehavior, true},
"UserPromptSubmit": {"gating", mechTopLevelDecision, true},
"UserPromptExpansion": {"gating", mechTopLevelDecision, true},
"PreCompact": {"gating", mechTopLevelDecision, true},
"ConfigChange": {"gating", mechTopLevelDecision, true},
"PostToolBatch": {"gating", mechTopLevelDecision, true},
"TaskCreated": {"gating", mechContinueFalse, true},
"TaskCompleted": {"gating", mechContinueFalse, true},
"TeammateIdle": {"gating", mechContinueFalse, true},
"Stop": {"gating", mechNeutral, false}, // 반전됨
"SubagentStop": {"gating", mechNeutral, false}, // 반전됨
"Elicitation": {"gating", mechNeutral, false}, // v1에서 연결되지 않음
"ElicitationResult": {"gating", mechNeutral, false},
// CONTEXT — additionalContext / 출력 다시 쓰기, 실제 차단 없음.
"PostToolUse": {"context", mechPostToolUse, true},
"PostToolUseFailure": {"context", mechNeutral, false},
"PermissionDenied": {"context", mechNeutral, false},
"MessageDisplay": {"context", mechNeutral, false},
"SessionStart": {"context", mechNeutral, false},
"Setup": {"context", mechNeutral, false},
"SubagentStart": {"context", mechNeutral, false},
"PostCompact": {"context", mechNeutral, false},
"InstructionsLoaded": {"context", mechNeutral, false},
// OBSERVE — 의사결정 제어 없음.
"Notification": {"observe", mechNeutral, false},
"SessionEnd": {"observe", mechNeutral, false},
"StopFailure": {"observe", mechNeutral, false},
"CwdChanged": {"observe", mechNeutral, false},
"FileChanged": {"observe", mechNeutral, false},
"WorktreeCreate": {"observe", mechNeutral, false},
"WorktreeRemove": {"observe", mechNeutral, false},
}
30개의 이벤트에는 각각 정확히 하나의 분류, 하나의 연결 메커니즘, 하나의 집행 가능성 판정이 포함됩니다. 렌더러는 hookMechFor(event)를 참조하여 내보낼 JSON 모양을 결정합니다. 결정자는 HookEnforcementFor(event)를 참조하여 정책 규칙을 적용할지, 컨텍스트를 삽입할지, 관찰할지 결정합니다. 둘 다 동일한 지도에서 읽으므로 의견이 다를 수 없습니다.
거부-폐쇄 기본값
파일에서 가장 중요한 기능은 4줄 길이입니다.
func hookSpecFor(event string) hookSpec {
if s, ok := hookSpecs[event]; ok {
return s
}
return hookSpec{class: "unknown", mech: mechPermissionDecision, enforceable: true}
}
맵에 없는 이벤트(Claude Code가 커넥터가 아직 분류하지 않은 새 후크 이벤트를 제공했기 때문에)는 unknown로 처리되고 mechPermissionDecision 연결 메커니즘이 할당되며 시행 가능으로 표시됩니다. 이것이 거부-폐쇄 기본값입니다. 인식되지 않은 이벤트는 자동 통과가 아닌 권한 게이트입니다. 일치하는 정책 규칙이 없으면 운영자가 구성한 기본 상태가 적용되며 관리형 배포에서는 거부됩니다.
기본적으로 중립 또는 관찰로 설정된 대안은 Claude Code가 도입하는 모든 새로운 후크 이벤트가 누군가가 이를 인지하고 지도에 추가할 때까지 제어되지 않음을 의미합니다. 거부 폐쇄 모델에서는 분류가 보수적이라 하더라도 새 이벤트가 발생하는 순간부터 관리됩니다. 새 이벤트에 대한 거짓 거부는 표시되고 수정 가능합니다. 자동 허용은 보이지 않으며 몇 달 동안 지속될 수 있습니다.
HookEnforcement 구조체는 결정자가 게이팅 이벤트의 세 가지 계층을 구별할 수 있도록 이 분류를 내보냅니다.
- 클래식 게이트(
PreToolUse,PermissionRequest,PostToolUse및 알 수 없는 이벤트): 일치하는 관리 규칙이 없으면 운영자의 정책 기본값이 적용됩니다(거부-닫힘). - 라이프사이클 게이트(
UserPromptSubmit,TaskCreated와 같은 기타 적용 가능한 게이팅 이벤트): 결정자는 이벤트별 안전 기본값을 대신 적용합니다. UX/lifecycle 이벤트의 경우 중립이고 상태 변경 이벤트의 경우 거부합니다. - 강제 불가능한 게이트(
Stop,SubagentStop,Elicitation): 결정자는 관계없이 중립을 반환합니다. Claude Code가 “계속 실행”으로 해석하는 거부를 내보내는 것은 안전의 반대입니다.
유통: 분류부터 차량까지
사건을 올바르게 분류하는 것은 절반입니다. 다른 하나는 모든 Claude Code 인스턴스에 PEP를 가져오는 것입니다. 관리 설정 커넥터는 후크 구성을 Claude Code가 예상하는 모양으로 렌더링하고 이를 서버 관리 설정 파일(제어 평면이 모든 관리 호스트에 푸시하는 재정의 불가능한 구성)로 배포합니다.
PEP 후크는 빈 일치자(모든 도구와 일치 - 어떤 도구도 적용 지점을 벗어나지 않음)와 함께 배포되며 개발자의 로컬 후크가 관리되는 PEP를 약화시키는 것을 방지하기 위해 allowManagedHooksOnly와 쌍을 이룹니다. 해당 플래그가 없으면 사용자 수준 후크가 관리되는 후크를 섀도잉할 수 있으며 우회가 가능한 동안 시행이 존재하는 것처럼 보입니다. 작성 시간 유효성 검사는 이를 포착합니다. 정책이 allowManagedHooksOnly 없이 PreToolUse PEP 후크를 제공하는 경우 콘솔은 변조 방지 권고를 발생시킵니다.
원격 측정 경로는 별도이며 계획에 따라 결정되지 않습니다. Claude Code의 OpenTelemetry 내보내기는 관리형 환경 변수(CLAUDE_CODE_ENABLE_TELEMETRY, OTEL_* 내보내기 키)를 통해 활성화되므로 제어 플레인은 추론을 프록시하거나 구독 자격 증명을 건드리지 않고도 구독 사용을 관찰할 수 있습니다. 콘텐츠 캡처(OTEL_LOG_USER_PROMPTS, OTEL_LOG_TOOL_CONTENT)는 기본값이 꺼진 상태입니다. 이를 켜는 것은 개발자의 컴퓨터에서 프롬프트 및 도구 콘텐츠를 제공하여 컨트롤 플레인이 소유해야 하는 상주 및 편집 의무를 생성하기 때문에 의도적이고 플래그가 지정된 선택입니다.
관리형 배포의 의미
관리되는 PEP를 사용하여 Claude Code를 실행하는 팀은 중요한 몇 가지 속성을 얻습니다.
- 자동 허용 없음. 모든 후크 이벤트는 분류되며, 인식되지 않은 모든 이벤트는 기본적으로 거부됩니다. 새로운 Claude Code 릴리스에서는 관리되지 않는 수명 주기 이벤트를 도입할 수 없습니다.
- 이벤트당 올바른 와이어 모양. PEP는 일반 JSON 객체를 반환하지 않으며 Claude Code가 이를 따르기를 바랍니다. 잘못된 스키마의 거부는 거부가 아니기 때문에 특정 이벤트가 예상하는 정확한 출력 스키마를 반환합니다.
- 정직한 비집행. 시행할 수 없는 이벤트(이벤트 관찰, 반전된 게이팅 이벤트)는 시행되는 척되지 않습니다. PEP는 이를 기록하고 중립을 반환하며 운영자에게 잘못된 제어 감각을 제공하지 않습니다.
- 배포 시 변조 방지. 관리 설정 레이어는 PEP 후크가 재정의 불가능하도록 보장하고 약화될 수 있는 구성에 플래그를 지정합니다.
PEP는 더 큰 관리 운영 모델의 집행 절반입니다. 액세스 맵, 감사 원장 및 그 주변의 코드형 정책 계층은 제품 개요 및 후크 문서에서 다룹니다. 원격 측정 경로와 권한 게이트가 어떻게 구성되는지 확인하려면 아키텍처 개요에서 두 가지를 모두 살펴보세요.