TL;DR
- Claude Code Mod는 파일 세 개로 이루어집니다.
.claude-plugin/plugin.json,{"modules":["./index.ts"]}를 담은hooks/hooks.json, 그리고register(on)을 export하는 TypeScript 모듈입니다. 로드하는 데는 이것만 있으면 됩니다. - 빌드 2.1.272에서
/plugin-types가 생성하는 타입 파일claude-code.d.ts는 11,783줄이고, 23개 명사에 걸친 84개의 이벤트 또는 호출 이름이 들어 있습니다.fs.readFile은 사라졌고fs.read와fs.write가 그 자리를 대신합니다. - 42줄짜리
tool.call훅 하나로 Read 도구와 Bash 도구 모두에서 모델이 실제 키 대신API_KEY=[REDACTED]를 읽게 만들었습니다. 정상 상태의 훅 한 번 왕복은 27.9 ms였고, 세션에 추가된 토큰은 약 0이었습니다. - 10초 예산을 넘겨 잠들거나 예외를 던지는 훅은 건너뛰어지고, 그 아래의 명령이 그대로 실행됩니다. 로그에는 남지만 효과는 우회입니다. 즉 가드 Mod는 fail open입니다.
- 생성 경로는 작동합니다. 문장 하나로 약 4분, $1.23에 190줄짜리 Mod와 53줄짜리 테스트가 나왔고,
claude plugin validate는 경고 없이 통과했으며claude plugin test는 4 pass / 0 fail이었습니다. - 재현되지 않은 것이 하나 있습니다. Mod는
claude -p에서는 로드됐지만, pty로 구동한 대화형 REPL에서는 두 번 시도해도 아무 반응이 없었습니다. 실제 터미널에서 직접 테스트하기 전까지 REPL 로딩은 미검증으로 보세요.
측정 결과가 말해 주는 것
아래 내용은 모두 CLAUDE_CODE_ENABLE_FUNCTION_HOOKS를 설정한 Claude Code 2.1.272에서, 직접 작성한 redact-secrets라는 Mod와 이를 깨뜨리려고 만든 임시 Mod 세 개로 실행한 결과입니다. 이 기능 자체는 Function Hooks 제안 이슈에서 추적되고 있으며, 지금으로서는 공식 스펙에 가장 가까운 문서입니다 s3.
도구는 문서보다 먼저 나와 있습니다. 빌드 2.1.272에는 claude plugin validate, test, eval, details가 들어 있고, plugin --help의 명령 블록에 test가 나오지 않는데도 test는 작동합니다 s3. 세션 안에서 /plugin-types를 실행하면 .claude/types/claude-code.d.ts에 11,783줄이 기록되고, 서버 7개의 MCP 도구 150개를 다루는 3,438줄짜리 claude-code-mcp.d.ts도 함께 생성됩니다 s3. 생성된 타입을 세어 보면 23개 명사에 걸쳐 84개의 이벤트 또는 호출 이름이 있고, 9월 글에서 읽었을 수 있는 한 가지가 바뀌어 있습니다. fs.readFile은 더 이상 존재하지 않고, 표면은 fs.read와 fs.write입니다 s9. 같은 타입 정의에 따르면 tool.call 훅은 { result, context? } 또는 { deny }를 반환합니다. text와 ref는 코어에서 오는 값이고 훅 자체의 응답에는 포함되지 않으므로, 다시 쓰는 대상은 text가 아니라 result입니다 s3.
claude plugin validate는 Mod가 실행되기도 전에 footprint를 출력합니다. ./index.ts hooks: tool.call, ./index.ts calls: $.ui.toast, 그리고 모듈이 호스트 기능을 전혀 건드리지 않으면 calls: nothing on $입니다 s4. 이 정적 출력은 awesome-claude-code-mods 같은 디렉터리가 오늘 자동화할 수 있는 유일한 검증이며, 다음 단락을 읽을 때 중요해집니다.
redaction은 -p 모드에서 작동합니다. 42줄짜리 훅이 tool.call을 가로챘고, 파일과 셸 출력에는 sk-test1234567890abcdef가 들어 있었는데 모델이 받은 것은 Read 도구와 Bash 도구 모두 API_KEY=[REDACTED]였습니다 s3. 디버그 로그에 기록된 정상 훅 한 번의 왕복은 워커 hop과 next()를 포함해 27.9 ms였습니다 s3. claude plugin details는 이 Mod가 매 세션에 추가하는 토큰을 약 0으로 계산했습니다. Mod는 프롬프트 텍스트가 아니라 프로세스 안의 코드이고, 이것이 Mod를 밀어 주는 쪽이 셸 훅과 스킬에 맞서 내세우는 핵심 논거입니다 s7.
가드는 fail open입니다. 15초 동안 잠드는 slow-guard Mod는 10초 예산에서 잘렸고 hook failed: slow-guard: exceeded 10000ms budget (tool.call; skipped; what is below it ran in its place)가 기록됐지만 echo hi는 그대로 실행됐습니다 s3. 예외를 던지는 throw-guard Mod도 같은 방식으로 건너뛰어졌고, hook failed: throw-guard: boom (tool.call; skipped; what is below it ran in its place)가 574.2 ms 시점에 보고됐습니다 s3. 로그에서는 시끄럽지만 효과로는 우회입니다. 무언가를 막는 것이 목적인 Mod라면 이 점을 염두에 두고 읽어야 합니다. 가드의 버그는 크래시가 아니라 구멍입니다.
생성은 저렴합니다. 문장 하나에서 모델이 약 4분, 34턴, $1.23(출력 토큰 19,053, 캐시 읽기 497,282, thinking 8,357)에 190줄짜리 동작하는 Mod와 53줄짜리 테스트를 작성했습니다 s3. 이 Mod는 claude plugin validate를 경고 없이 통과했고 claude plugin test에서 0.31초에 4 pass / 0 fail이었으며, 키를 실시간으로 숨겼습니다 s3. 직접 작성한 Mod에서는 claude plugin test가 API 키도 모델 호출도 없이 0.25초에 1 pass로 실행됐습니다 s3. 템플릿으로 같은 실험을 반복하고 싶다면 에이전트에게 Mod 작성법을 가르치는 스킬이 이미 있습니다 s12.
재현되지 않은 것은 이렇습니다. pty로 구동한 REPL에서는 두 번 시도하는 동안 Mod가 로드되지 않아 $.ui.toast가 한 번도 렌더링되지 않았고, -p에서는 같은 동작이 디버그 줄로만 나타났습니다. X에서 도는 "REPL 전용"이라는 주장과 반대되는 결과가 관찰됐지만 근본 원인은 분리하지 못했습니다 s8. 5초짜리 멈춘 워커 heartbeat도 테스트하지 않았고, 10초 await 예산과 throw 경로만 측정했습니다.
측정표
| 케이스 | Mod가 하는 일 | 결과 | 시간 |
|---|---|---|---|
| redact-secrets, Read 도구 | tool.call에서 result를 다시 씀 |
모델에게 API_KEY=[REDACTED]가 보임 |
hop당 27.9 ms |
| redact-secrets, Bash 도구 | 같은 훅, 셸 출력 | 모델에게 API_KEY=[REDACTED]가 보임 |
hop당 27.9 ms |
| slow-guard | tool.call 안에서 15초 sleep |
건너뛰어짐, echo hi 실행됨 |
10000 ms에서 잘림 |
| throw-guard | tool.call 안에서 boom을 throw |
건너뛰어짐, 명령 실행됨 | 574.2 ms |
| 생성된 Mod | 문장 하나로 190줄 + 테스트 53줄 | validate: 경고 없음, test: 4 pass / 0 fail | 약 4분, 34턴, $1.23 |
redact-secrets에 대한 claude plugin test |
API 키 없음, 모델 호출 없음 | 1 pass | 0.25초 |
claude plugin details |
Mod의 세션 비용 | 추가 토큰 약 0 | n/a |
프로토콜: Claude Code 2.1.272, 환경 변수로 function hooks 활성화. 각 Mod는 plugin.json, hooks/hooks.json, index.ts 하나를 가진 플러그인 디렉터리입니다. 실행은 디버그 로그를 켠 claude -p로 했고, 준비한 파일과 셸 명령 모두에 sk-test1234567890abcdef가 들어 있었습니다. fail-open 케이스는 15초 sleep하는 Mod와 예외를 던지는 Mod로 구동했고, 가드 대상 명령은 echo hi였습니다. 대화형 REPL은 pty로 구동했으며 두 번 시도에서 Mod를 로드하지 못했습니다.
월요일에 할 일
- 세션에서
/plugin-types를 실행하고.claude/types/claude-code.d.ts를 여세요. 9월 글의 스니펫을 믿기 전에fs.read와tool.call을 검색합니다. - 파일 세 개짜리 Mod 뼈대(
plugin.json,{"modules":["./index.ts"]}를 담은hooks/hooks.json,register(on)을 export하는index.ts)를 작성하고claude plugin validate를 실행하세요.hooks:와calls:footprint 줄을 읽습니다. - 가장 많이 쓰는 셸 훅을
result를 다시 쓰는tool.call핸들러로 옮기고, 디버그 로그의 hop 시간을 셸 버전과 비교하세요. - Mod 옆에
claude plugin test파일을 추가해서 CI에서 API 키 없이 가드가 실행되게 하세요. - 모든 가드 핸들러를 try/catch로 감싸고 실패 시
{ deny }를 반환하게 하세요. 이 빌드에서는 예외나 10초 정체가 발생하면 가드가 건너뛰어지고 명령이 통과합니다. - Mod를
claude -p와 실제 대화형 터미널에서 각각 테스트하고, 어느 쪽에서 로드됐는지 기록해 두세요. - 서드파티 Mod를 설치하기 전에
claude plugin validate를 실행하고, 그 Mod가 쓸 이유가 없는 호스트 기능을calls:줄에 나열하는 것은 거부하세요.
더 알아보기
- 제안 이슈를 댓글에 첨부된 아키텍처 PDF까지 끝까지 읽어 보세요.
register(on), 예산, 워커 hop에 대해 문서로 남은 유일한 계약입니다 s3. - 호스트 프로세스 안에서
ModApi에 대해 TypeScript를 실행하는 Command Code Mods 설계와 비교해 보세요. 두 시스템은 구조와 early-access 제한을 공유합니다 s2. - claudefa.st 프리뷰는 기능이 아직 제안 단계일 때 쓰였습니다. 9월 3일 글과 2.1.272 바이너리 사이에서 무엇이 바뀌었는지 보기에 좋습니다 s9.
- Prathkum의 노트 트윗은 in-process 훅이 토큰과 지연 시간에서 셸 훅보다 나은 이유를 가장 명확하게 짧게 설명합니다 s7.
- cc-mod-waitwhat은 처음 읽어 볼 만한 Mod입니다. 프롬프트 위에 UI를 그리고 트랜스크립트에는 아무것도 쓰지 않습니다 s11.
- cc-arcade는
$.ui가 어디까지 닿는지 보여 줍니다. Mod에서 프롬프트 위에 게임을 렌더링합니다 s5. - hooks 레퍼런스는 아직 셸 모델을 설명합니다. 예전 이벤트를 새
noun.event이름에 대응시키려면 열어 두세요 s1. - X의 회의론자들은 플러그인이 이미 이 역할을 하고 있고 표면이 릴리스마다 깨질 거라고 주장합니다. 이름이 바뀐
fs명사가 그 근거 중 하나입니다 s23.
출처
- Function Hooks proposal (issue #91870), GitHub, anthropics/claude-code. 읽을 이유: Mod에 대한 유일한 스펙 형태의 글이며, 댓글에 아키텍처 PDF와 출시 경과가 있습니다.
- Hooks reference, Claude Code docs. 읽을 이유: 지금 옮겨 가는 출발점인 셸 훅 모델을 이벤트별로 설명합니다.
- Command Code Mods documentation, Command Code. 읽을 이유: 같은 TypeScript in-process 구조의 선행 사례로, Anthropic이 무엇을 따랐고 무엇을 피했는지 파악하는 데 유용합니다.
- awesome-claude-code-mods, GitHub, karanb192. 읽을 이유: 공개 Mod를 자동 스캔한 디렉터리이며, 검증 수단은 validate footprint뿐입니다.
- cc-arcade, GitHub, sezaakgun. 읽을 이유: 이 기능을 눈에 보이게 만든 데모이자
$.ui둘러보기입니다. - Boris Cherny announcement tweet, X, Boris Cherny. 읽을 이유: 블로그 글이 없으므로 Anthropic 엔지니어가 직접 밝힌 출시 발표입니다.
- Prathkum: Function Hooks explained, X, Prathkum. 읽을 이유: 이미 셸 훅을 쓰는 사람을 위한 훅과 Mod의 차이에 대한 가장 좋은 짧은 설명입니다.
- shipnotesai reaction thread, X, shipnotesai. 읽을 이유: "REPL 전용"이라는 주장이 돌던 곳이며, 우리 실행 결과는 이와 달랐습니다.
- Claude Code Function Hooks: Preview Behind a Flag, claudefa.st. 읽을 이유: 출시 전 해설로, 제안서와 바이너리를 대조해 보기에 좋습니다.
- cc-mod-waitwhat, GitHub, GGGODLIN. 읽을 이유: 트랜스크립트가 아니라 UI에 쓰는, 작고 읽기 쉬운 Mod입니다.
- claude-mods-skill, GitHub, BeLazy167. 읽을 이유: Mod 생성용 스킬로, 문장 하나 실험을 직접 반복해 보고 싶을 때 씁니다.
- AxialisSoftware reaction, X, AxialisSoftware. 읽을 이유: 트윗 하나에 담긴 회의론 쪽 주장입니다.
FAQ
지금 Mod가 내 셸 훅을 대체하나요?
무언가를 막아야 하는 용도로는 아직 아닙니다. 2.1.272에서는 예외를 던지거나 10초를 넘겨 멈춘 훅이 건너뛰어지고 명령이 실행됩니다. 셸 훅은 계속 작동하므로, 차단을 맡은 훅은 선언형 catch나 fail-closed 옵션이 생길 때까지 셸에 두세요.
Mod가 잘 돌아가는데 validate가 왜 중요한가요?
hooks:와 calls: 줄이 Mod가 $에서 무엇을 건드리는지 보여 주는 유일한 정적 뷰이기 때문입니다. 직접 만든 Mod라면 footprint를 확인해 주고, 서드파티 Mod라면 코드가 내 프로세스에서 실행되기 전에 받는 검토의 전부입니다.
Mod는 세션당 비용이 얼마나 드나요?
claude plugin details는 추가 토큰을 약 0으로 보고했습니다. Mod는 프롬프트 안의 텍스트가 아니라 엔진에서 실행되는 코드이고, 이것이 스킬이나 CLAUDE.md 규칙에 비해 갖는 가장 큰 장점입니다.
왜 Mod가 -p에서는 로드되고 REPL에서는 로드되지 않았나요?
알 수 없습니다. pty로 구동한 두 번의 시도에서는 아무 반응이 없었고 claude -p에서는 훅이 로드되어 적용됐습니다. 원인은 기능이 아니라 pty 환경일 가능성이 높으니, 어느 쪽이든 의존하기 전에 직접 쓰는 터미널에서 테스트하세요.
AIDive