2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
3> Use this file to discover all available pages before exploring further.3> Use this file to discover all available pages before exploring further.
4 4
5# 이벤트에 모드로 반응하기5# mod로 이벤트에 반응하기
6 6
7> 모드에서 Claude Code 이벤트 처리: 도구 호출, 프롬프트, 턴 관찰, 재작성 또는 응답, 훅이 처리하는 이벤트 필터링, 다른 모드 계획7> mod에서 Claude Code 이벤트를 처리합니다. 도구 호출, 프롬프트, 턴을 관찰하거나 재작성하거나 직접 응답하고, 훅이 처리할 이벤트를 필터링하며, 다른 mod와의 공존을 계획합니다.
8 8
9훅은 이벤트 핸들러입니다. Claude Code가 명명된 이벤트가 발생할 때 실행하는 함수입니다. Claude Code는 도구를 실행하거나, 프롬프트를 제출하거나, 모델에 요청을 보내거나, 세션을 시작하거나 종료할 때와 같이 행동하려고 할 때마다 이벤트를 발생시킵니다. 훅은 Claude Code가 행동하기 전에 실행되므로 이벤트를 관찰하거나, 재작성하거나, Claude Code를 대신하여 응답할 수 있습니다. [`on(eventName, handler)`](/docs/ko/plugins/mods/reference#the-hook-function)로 훅을 등록합니다.9훅은 이벤트 핸들러, 즉 이름이 지정된 이벤트가 발생할 때 Claude Code가 실행하는 함수입니다. Claude Code는 도구를 실행하거나, 프롬프트를 제출하거나, 모델에 요청을 보내거나, 세션을 시작하거나 종료하는 등 작업을 수행하려는 각 시점에 이벤트를 발생시킵니다. 훅은 Claude Code가 작업을 수행하기 전에 실행되므로 이벤트를 관찰하거나, 재작성하거나, Claude Code를 대신하여 응답할 수 있습니다. 훅은 [`on(eventName, handler)`](/docs/ko/plugins/mods/reference#the-hook-function)로 등록합니다.
10 10
11여기서 시작하기 전에 [첫 번째 모드를 빌드](/docs/ko/plugins/mods/create)하세요. 모든 이벤트와 정확한 필드는 [참조](/docs/ko/plugins/mods/reference#events)를 보거나 [빌드의 타입을 읽으세요](/docs/ko/plugins/mods/create#get-the-types-for-your-build).11여기서 시작하기 전에 [첫 번째 mod](/docs/ko/plugins/mods/create)를 먼저 만드십시오. 모든 이벤트와 정확한 필드는 [레퍼런스](/docs/ko/plugins/mods/reference#events)를 참조하거나 [사용 중인 빌드의 타입](/docs/ko/plugins/mods/create#get-the-types-for-your-build)을 확인하십시오.
12 12
13<h2 id="how-a-hook-handles-an-event">13<h2 id="how-a-hook-handles-an-event">
14 훅이 이벤트를 처리하는 방법14 훅이 이벤트를 처리하는 방식
15</h2>15</h2>
16 16
17훅은 이벤트와 Claude Code가 그것에 대해 할 일 사이에 있으므로 이벤트를 관찰하거나, 재작성하거나, 직접 응답할 수 있습니다. 세 가지 인수를 받습니다: [mods API](/docs/ko/plugins/mods/api)를 `$`로, 이벤트를 `e`로, 다음 핸들러를 `next`로 받습니다. 이벤트의 핸들러들은 미들웨어 체인을 형성합니다. `next(e)`는 다음 핸들러를 호출하는데, 이는 다른 모드의 훅이거나 체인의 끝에서 Claude Code 자신의 동작이며, 결과로 해결됩니다. 훅이 `next`로 무엇을 하는지가 세 가지 중 어느 것을 하는지 결정합니다.17훅은 이벤트와 그 이벤트에 대해 Claude Code가 수행할 동작 사이에 위치하므로, 이벤트를 관찰하거나, 다시 작성하거나, 직접 응답할 수 있습니다. 훅은 세 개의 인수를 받습니다. `$`로 전달되는 [mods API](/docs/ko/plugins/mods/api), `e`로 전달되는 이벤트, `next`로 전달되는 다음 핸들러입니다. 하나의 이벤트에 대한 핸들러들은 미들웨어 체인을 구성합니다. `next(e)`는 다음 핸들러를 호출하며, 이 핸들러는 다른 mod의 훅이거나 체인의 끝에서는 Claude Code 자체의 동작입니다. `next(e)`는 그 결과로 resolve됩니다. 훅이 `next`로 무엇을 하느냐에 따라 세 가지 중 어느 것을 수행할지가 결정됩니다.
18 18
19<h3 id="observe-an-event">19<h3 id="observe-an-event">
20 이벤트 관찰하기20 이벤트 관찰하기
21</h3>21</h3>
22 22
23이벤트를 변경하지 않고 관찰하려면 작업을 수행하고 `next(e)`를 반환합니다. 이 훅은 Claude가 사용하려고 하는 각 도구를 기록합니다:23이벤트를 변경하지 않고 관찰하려면 원하는 작업을 수행한 뒤 `next(e)`를 반환합니다. 다음 훅은 Claude가 사용하려는 각 도구를 로그에 기록합니다.
24 24
25```javascript theme={null}25```javascript theme={null}
26on('tool.call', async ($, e, next) => {26on('tool.call', async ($, e, next) => {
27 // 도구가 실행되기 전에 실행됨27 // Runs before the tool does
28 $.ui.log('Claude is about to use ' + e.tool)28 $.ui.log('Claude is about to use ' + e.tool)
29 // 이벤트를 변경하지 않고 전달29 // Pass the event on unchanged
30 return next(e)30 return next(e)
31})31})
32```32```
33 33
34각 도구가 실행되기 전에 `● my-mod: Claude is about to use Bash`와 같은 흐릿한 줄이 트랜스크립트에 나타나며, 여기서 `my-mod`는 플러그인의 이름입니다. 도구는 모드 없이 실행되는 것처럼 실행됩니다.34각 도구가 실행되기 전에 `● my-mod: Claude is about to use Bash`와 같은 흐린 줄이 트랜스크립트에 표시되며, 여기서 `my-mod`는 플러그인 이름입니다. 도구는 mod가 없을 때와 동일하게 실행됩니다.
35 35
36이벤트 후에 행동하려면 `await next(e)`를 하고, 작업을 수행한 후 결과를 반환합니다. 이 훅은 각 도구가 실행된 후에 기록합니다:36이벤트 이후에 동작하려면 `await next(e)`를 실행하고, 원하는 작업을 수행한 다음 결과를 반환합니다. 다음 훅은 각 도구가 실행된 후에 로그에 기록합니다.
37 37
38```javascript theme={null}38```javascript theme={null}
39on('tool.call', async ($, e, next) => {39on('tool.call', async ($, e, next) => {
40 // 도구를 실행하고 결과를 기다림40 // Let the tool run, and wait for its result
41 const result = await next(e)41 const result = await next(e)
42 // 도구가 실행된 후에 실행됨42 // Runs after the tool does
43 $.ui.log(e.tool + ' finished')43 $.ui.log(e.tool + ' finished')
44 // 결과를 변경하지 않고 반환44 // Give the result back unchanged
45 return result45 return result
46})46})
47```47```
48 48
49이제 줄은 각 도구가 완료된 후에 나타납니다. Claude는 훅이 `next(e)`가 해결된 것을 반환하기 때문에 어느 쪽이든 같은 결과를 읽습니다.49이제 각 도구가 완료된 후에 해당 줄이 표시됩니다. 훅이 `next(e)`가 resolve된 값을 그대로 반환하므로, 어느 경우든 Claude는 동일한 결과를 읽습니다.
50 50
51<h3 id="rewrite-an-event">51<h3 id="rewrite-an-event">
52 이벤트 재작성하기52 이벤트 다시 작성하기
53</h3>53</h3>
54 54
55Claude Code가 행동하는 것을 변경하려면, 예를 들어 프롬프트의 텍스트를 변경하려면 수정된 이벤트 복사본으로 `next`를 호출합니다. 이벤트 자체는 불변입니다: 모든 깊이에서 동결되어 있으며, 필드에 할당하면 오류가 발생합니다. 이 훅은 각 프롬프트를 전송하기 전에 트림합니다:55프롬프트의 텍스트처럼 Claude Code가 처리하는 대상을 변경하려면, 이벤트의 수정된 복사본으로 `next`를 호출합니다. 이벤트 자체는 불변입니다. 깊게 동결(deeply frozen)되어 있어 필드에 값을 할당하면 오류가 발생합니다. 다음 훅은 각 프롬프트가 전송되기 전에 앞뒤 공백을 제거합니다.
56 56
57```javascript theme={null}57```javascript theme={null}
58on('prompt.submit', async ($, e, next) => {58on('prompt.submit', async ($, e, next) => {
59 // 텍스트가 변경된 이벤트의 복사본을 전달59 // Pass on a copy of the event with its text changed
60 return next({ ...e, text: e.text.trim() })60 return next({ ...e, text: e.text.trim() })
61})61})
62```62```
63 63
64나중의 핸들러와 Claude Code는 트림된 프롬프트를 받고 원본을 절대 보지 않습니다. 결과를 변경할 수도 있습니다: `await next(e)`를 한 후 필드가 바뀐 결과의 복사본을 반환합니다.64이후의 핸들러와 Claude Code는 공백이 제거된 프롬프트를 받으며 원본은 보지 않습니다. 결과를 변경할 수도 있습니다. `await next(e)`를 실행한 다음, 필드 하나를 교체한 결과의 복사본을 반환하면 됩니다.
65 65
66<h3 id="answer-an-event">66<h3 id="answer-an-event">
67 이벤트에 응답하기67 이벤트에 응답하기
68</h3>68</h3>
69 69
70이벤트를 직접 처리하려면 `next`를 호출하지 않고 결과를 반환합니다. 이는 체인을 단락시키므로 나중의 모드와 Claude Code의 자신의 동작이 실행되지 않습니다. 이 훅은 모든 Bash 명령을 거부합니다:70이벤트를 직접 처리하려면 `next`를 호출하지 않고 결과를 반환합니다. 이렇게 하면 체인이 단락(short-circuit)되므로 이후의 mod와 Claude Code 자체의 동작이 실행되지 않습니다. 다음 훅은 모든 Bash 명령을 거부합니다.
71 71
72```javascript theme={null}72```javascript theme={null}
73on('tool.call', { tool: 'Bash' }, async () => {73on('tool.call', { tool: 'Bash' }, async () => {
74 // next를 호출하지 않으므로 명령이 실행되지 않음74 // No call to next, so the command never runs
75 return { deny: 'Bash is turned off in this project. Use the file tools.' }75 return { deny: 'Bash is turned off in this project. Use the file tools.' }
76})76})
77```77```
78 78
79Claude가 Bash 명령을 시도할 때 명령이 실행되지 않으며, Claude는 `deny` 텍스트를 도구의 결과로 읽습니다. 각 이벤트는 자신의 결과 형태를 가지고 있으며, [이벤트 참조](/docs/ko/plugins/mods/reference#events)에 나열되어 있습니다.79Claude가 Bash 명령을 시도하면 명령은 실행되지 않으며, Claude는 `deny` 텍스트를 도구의 결과로 읽습니다. 각 이벤트에는 고유한 결과 형태가 있으며, [이벤트 레퍼런스](/docs/ko/plugins/mods/reference#events)에 나열되어 있습니다.
80 80
81<h3 id="filter-which-events-a-hook-handles">81<h3 id="filter-which-events-a-hook-handles">
82 훅이 처리하는 이벤트 필터링하기82 훅이 처리할 이벤트 필터링하기
83</h3>83</h3>
84 84
85훅을 일부 이벤트에만 실행하려면 `on`의 두 번째 인수로 필터를 전달합니다. Claude Code는 필터를 매처라고 부릅니다. 이는 필드가 이벤트의 필드와 비교되는 객체이며, 모든 필드가 일치할 때만 훅이 실행됩니다. 필드는 값, 허용된 값의 배열, 또는 정규 표현식일 수 있습니다.85일부 이벤트에 대해서만 훅을 실행하려면 `on`의 두 번째 인수로 필터를 전달합니다. Claude Code에서는 이 필터를 matcher라고 부릅니다. matcher는 필드를 이벤트의 필드와 비교하는 객체이며, 모든 필드가 일치할 때만 훅이 실행됩니다. 필드 값으로는 단일 값, 허용되는 값의 배열, 또는 정규 표현식을 사용할 수 있습니다.
86 86
87이 예제의 각 줄은 같은 함수 `hook`을 더 좁은 도구 호출 집합에 등록합니다:87다음 예시의 각 줄은 동일한 함수 `hook`을 더 좁은 범위의 도구 호출에 대해 등록합니다.
88 88
89```javascript theme={null}89```javascript theme={null}
90// 문자열은 하나의 값과 일치: Bash 호출만90// A string matches one value: Bash calls only
91on('tool.call', { tool: 'Bash' }, hook)91on('tool.call', { tool: 'Bash' }, hook)
92// 배열은 그 안의 모든 값과 일치: Edit 호출과 Write 호출92// An array matches any value in it: Edit calls and Write calls
93on('tool.call', { tool: ['Edit', 'Write'] }, hook)93on('tool.call', { tool: ['Edit', 'Write'] }, hook)
94// 정규 표현식은 패턴으로 일치: 하나의 MCP 서버의 모든 도구94// A regular expression matches by pattern: every tool of one MCP server
95on('tool.call', { tool: /^mcp__github__/ }, hook)95on('tool.call', { tool: /^mcp__github__/ }, hook)
96```96```
97 97
98`hook`은 Bash, Edit, 또는 Write 호출에 대해 한 번씩 실행되며, 이름이 `mcp__github__`로 시작하는 도구에 대한 호출에 대해 한 번씩 실행됩니다. Read와 같은 다른 도구에 대한 호출은 세 가지 중 어느 것도 일치하지 않으므로 `hook`은 그것에 대해 실행되지 않습니다.98`hook`은 Bash, Edit, Write 호출에 대해 한 번, 그리고 이름이 `mcp__github__`로 시작하는 도구의 호출에 대해 한 번 실행됩니다. Read와 같은 다른 도구의 호출은 세 가지 중 어느 것과도 일치하지 않으므로 `hook`이 실행되지 않습니다.
99 99
100이벤트 이름은 와일드카드일 수 있습니다. `'classic.*'`는 모든 [설정 훅 이벤트](#hook-the-settings-hook-events)와 일치합니다. `'*'`는 [텔레메트리 이벤트](/docs/ko/plugins/mods/reference#telemetry)를 제외한 모든 이벤트와 일치하며, 이는 이름으로 또는 `'telemetry.*'`로 훅합니다.100이벤트 이름에는 와일드카드를 사용할 수 있습니다. `'classic.*'`는 모든 [설정 훅 이벤트](#hook-the-settings-hook-events)와 일치합니다. `'*'`는 [텔레메트리 이벤트](/docs/ko/plugins/mods/reference#telemetry)를 제외한 모든 이벤트와 일치하며, 텔레메트리 이벤트는 고유한 이름과 `{ to: 'collector' }` 필터를 사용합니다.
101 101
102각 이벤트를 매처당 한 번씩 등록합니다. 매처 없이 `session.start`에 대해 `on`을 두 번 호출하면 모듈이 `on("session.start") is registered twice without a matcher`로 로드되지 않습니다. 모드가 세션 시작 시 수행하는 모든 것을 하나의 훅에 넣으세요.102각 이벤트는 matcher당 한 번씩 등록합니다. matcher 없이 `session.start`에 대해 `on`을 두 번 호출하면 모듈이 `on("session.start") is registered twice without a matcher` 오류와 함께 로드에 실패합니다. 세션 시작 시 mod가 수행하는 모든 작업은 하나의 훅에 넣으십시오.
103 103
104<h2 id="hook-what-claude-is-doing">104<h2 id="hook-what-claude-is-doing">
105 Claude가 하는 일을 훅하기105 Claude가 하는 작업에 훅 연결하기
106</h2>106</h2>
107 107
108이 이벤트들을 훅하여 도구 호출, 프롬프트, 또는 턴이 발생할 때 보거나 변경합니다. 모든 이벤트와 훅이 반환할 수 있는 것은 [이벤트 참조](/docs/ko/plugins/mods/reference#events)를 보세요.108이 이벤트들을 처리하면 도구 호출, 프롬프트, 턴이 진행되는 동안 이를 확인하거나 변경할 수 있습니다. 모든 이벤트와 훅이 반환할 수 있는 값은 [이벤트 레퍼런스](/docs/ko/plugins/mods/reference#events)를 참조하세요.
109 109
110<h3 id="guard-or-change-a-tool-call">110<h3 id="guard-or-change-a-tool-call">
111 도구 호출 보호 또는 변경하기111 도구 호출 차단 또는 변경하기
112</h3>112</h3>
113 113
114`tool.call` 훅은 Claude가 사용하려고 하는 각 도구를 보므로 호출을 거부하거나, 인수를 변경하거나, 통과시킬 수 있습니다. `tool.call`은 Claude Code가 도구를 실행하려고 할 때 발생하며, 서브에이전트가 만드는 호출과 MCP 도구에 대한 호출을 포함합니다. `e.tool`은 도구의 이름이고 도구의 인수는 `e`의 필드입니다. 예를 들어 Bash의 경우 `e.command`입니다. `next(e)`를 호출하면 Claude Code는 권한 확인을 실행한 후 도구를 실행합니다.114`tool.call` 훅은 Claude가 사용하려는 각 도구를 확인하므로 호출을 거부하거나, 인수를 변경하거나, 그대로 통과시킬 수 있습니다. `tool.call`은 Claude Code가 도구를 실행하려 할 때 발생하며, 서브에이전트가 수행하는 호출과 MCP 도구 호출도 포함됩니다. `e.tool`은 도구의 이름이고, 도구의 인수는 Bash의 `e.command`처럼 `e`의 필드입니다. `next(e)`를 호출하면 Claude Code가 권한 검사를 실행한 다음 도구를 실행합니다.
115 115
116이 훅은 강제 푸시하는 Bash 명령을 거부하고 Claude에게 이유를 알립니다:116다음 훅은 강제 푸시하는 Bash 명령을 거부하고 Claude에게 그 이유를 알려 줍니다.
117 117
118```javascript theme={null}118```javascript theme={null}
119// 매처는 훅을 Bash 호출로 제한하므로 e.command는 셸 명령119// The matcher limits the hook to Bash calls, so e.command is the shell command
120on('tool.call', { tool: 'Bash' }, async ($, e, next) => {120on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
121 if (/git push .*--force/.test(e.command)) {121 if (/git push .*--force/.test(e.command)) {
122 // next를 호출하지 않고 반환하면 이벤트에 응답하므로 명령이 실행되지 않음122 // Returning without calling next answers the event, so the command never runs
123 return { deny: 'Force pushes are not allowed in this repository. Push to a new branch instead.' }123 return { deny: 'Force pushes are not allowed in this repository. Push to a new branch instead.' }
124 }124 }
125 // 다른 모든 명령은 권한 확인을 거쳐 Bash로 진행125 // Every other command goes on to the permission check and then to Bash
126 return next(e)126 return next(e)
127})127})
128```128```
129 129
130Claude가 `git push --force`를 시도할 때 명령이 실행되지 않으며 훅이 `next`를 호출하지 않기 때문에 권한 프롬프트가 나타나지 않습니다. Claude는 `deny` 텍스트를 도구의 결과로 읽으므로 Claude가 행동할 수 있는 지시로 작성하세요. 다른 모든 Bash 명령은 모드 없이 실행되는 것처럼 실행됩니다.130Claude가 `git push --force`를 시도하면 훅이 `next`를 호출하지 않으므로 명령이 실행되지 않고 권한 프롬프트도 표시되지 않습니다. Claude는 `deny` 텍스트를 도구의 결과로 읽으므로, Claude가 따를 수 있는 지시문 형태로 작성해야 합니다. 그 밖의 모든 Bash 명령은 mod가 없을 때와 동일하게 실행됩니다.
131 131
132도구가 실행된 후에 행동하려면 `await next(e)`를 하고, 작업을 수행한 후 `next`가 준 것을 반환합니다. 이 훅은 Claude가 변경하는 각 `.mdx` 파일을 [`$.ui.log`](/docs/ko/plugins/mods/api#show-something-without-starting-a-turn)로 기록하며, 이는 Claude가 읽지 않는 흐릿한 줄을 트랜스크립트에 추가합니다:132도구가 실행된 후에 작업하려면 `await next(e)`를 수행하고, 필요한 작업을 한 다음, `next`가 반환한 값을 반환합니다. 다음 훅은 Claude가 변경하는 각 `.mdx` 파일을 [`$.ui.log`](/docs/ko/plugins/mods/api#show-something-without-starting-a-turn)로 로그에 기록합니다. 이 함수는 Claude가 읽지 않는 흐린 줄을 트랜스크립트에 추가합니다.
133 133
134```javascript theme={null}134```javascript theme={null}
135on('tool.call', { tool: ['Edit', 'Write'] }, async ($, e, next) => {135on('tool.call', { tool: ['Edit', 'Write'] }, async ($, e, next) => {
136 // 권한 확인과 도구를 기다리고 그들이 생산한 것을 유지136 // Wait for the permission check and the tool, and keep what they produced
137 const result = await next(e)137 const result = await next(e)
138 // 거부된 호출은 { deny }로 돌아오고, 실패한 것은 isError가 설정됨138 // A refused call comes back as { deny }, and a failed one has isError set
139 const changed = !result.deny && !result.isError139 const changed = !result.deny && !result.isError
140 if (changed && e.file_path.endsWith('.mdx')) $.ui.log('Claude changed ' + e.file_path)140 if (changed && e.file_path.endsWith('.mdx')) $.ui.log('Claude changed ' + e.file_path)
141 // 결과를 받은 대로 반환하므로 Claude는 도구가 반환한 것을 읽음141 // Return the result as it came, so Claude reads what the tool returned
142 return result142 return result
143})143})
144```144```
145 145
146Claude가 `.mdx` 파일을 편집하거나 쓴 후 트랜스크립트의 흐릿한 줄이 파일의 이름을 지정합니다. 다른 종류의 파일이나 거부되거나 실패한 호출에 대해서는 아무것도 기록되지 않습니다. 훅이 받은 결과를 반환하기 때문에 호출에 대한 Claude의 보기는 변경되지 않습니다.146Claude가 `.mdx` 파일을 편집하거나 작성하면 트랜스크립트의 흐린 줄에 해당 파일 이름이 표시됩니다. 다른 종류의 파일이나 거부되거나 실패한 호출은 로그에 기록되지 않습니다. 훅이 받은 결과를 그대로 반환하므로 Claude가 보는 호출 내용은 변하지 않습니다.
147 147
148호출을 변경하려면 변경된 인수를 `next`에 전달합니다. 호출을 다시 시도하려면 `next(e)`를 다시 호출합니다: 첫 번째 결과에서 `isError`를 보는 훅은 도구를 두 번째로 실행하고 그 결과를 반환할 수 있습니다. 호출에 직접 응답하려면 `next`를 호출하지 않고 `result` 필드가 있는 객체를 반환합니다. 예를 들어 `{ result: 'Skipped by my-mod' }`입니다. 그렇게 하면 권한 프롬프트가 나타나지 않으며 도구가 실행되지 않으므로 반환하는 결과가 Claude가 무슨 일이 일어났는지에 대해 배우는 모든 것입니다.148호출을 변경하려면 변경된 인수를 `next`에 전달합니다. 호출을 재시도하려면 `next(e)`를 다시 호출합니다. 첫 번째 결과에서 `isError`를 확인한 훅은 도구를 한 번 더 실행하고 그 결과를 반환할 수 있습니다. 호출에 직접 응답하려면 `next`를 호출하지 않고 `{ result: 'Skipped by my-mod' }`처럼 `result` 필드가 있는 객체를 반환합니다. 이렇게 하면 권한 프롬프트가 표시되지 않고 도구도 실행되지 않으므로, 반환한 결과가 Claude가 해당 상황에 대해 알게 되는 전부입니다.
149 149
150조직의 [관리 설정](/docs/ko/server-managed-settings)의 훅은 모든 모드의 `tool.call` 훅 전에 실행되며, 그 중 하나의 블록은 최종입니다.150조직의 [관리형 설정](/docs/ko/server-managed-settings)에 있는 훅은 모든 mod의 `tool.call` 훅보다 먼저 실행되며, 이 훅 중 하나의 차단은 최종적입니다.
151 151
152<h4 id="hold-a-tool-call-until-the-user-decides">152<h4 id="hold-a-tool-call-until-the-user-decides">
153 사용자가 결정할 때까지 도구 호출 보류하기153 사용자가 결정할 때까지 도구 호출 보류하기
154</h4>154</h4>
155 155
156훅은 도구 호출을 일시 중지하고 진행하기 전에 사용자에게 무엇을 할지 물어볼 수 있습니다. `tool.call` 훅은 `next`를 호출하거나 반환하기 전에 `await`할 수 있으며, 도구 호출은 그때까지 보류됩니다. 사용자에게 질문을 하려면 `$.ui.ask`를 호출합니다. 이는 Claude가 당신에게 무언가를 물어보는 데 사용하는 대화 상자에서 번호가 매겨진 옵션 목록 위에 질문을 표시하고 사용자가 선택한 레이블로 해결됩니다. 옵션 후에 대화 상자는 다른 답변을 입력하기 위한 행과 **Chat about this** 행을 추가합니다.156훅은 도구 호출을 일시 중지하고 진행하기 전에 사용자에게 어떻게 할지 물을 수 있습니다. `tool.call` 훅은 `next`를 호출하거나 반환하기 전에 `await`할 수 있으며, 그때까지 도구 호출은 대기 상태로 유지됩니다. 사용자에게 질문하려면 `$.ui.ask`를 호출합니다. 이 함수는 Claude가 사용자에게 질문할 때 사용하는 대화 상자에서 질문을 번호가 매겨진 옵션 목록 위에 표시하고, 사용자가 선택한 레이블로 resolve됩니다. 대화 상자는 옵션 뒤에 다른 답변을 입력하는 행과 **Chat about this** 행을 추가합니다.
157 157
158이 예제의 `RISKY` 패턴은 `rm -r`, `rm -rf`, `git reset --hard`, 그리고 `--force`가 있는 `git push`와 일치하며, `git push -f`와 같은 다른 철자는 놓칩니다. 이 모듈은 패턴과 일치하는 Bash 명령을 실행하기 전에 물어봅니다:158이 예제의 `RISKY` 패턴은 `rm -r`, `rm -rf`, `git reset --hard`, 그리고 `--force`가 포함된 `git push`와 일치하며, `git push -f` 같은 다른 표기는 놓칩니다. 이 모듈은 패턴과 일치하는 Bash 명령을 실행하기 전에 확인을 요청합니다.
159 159
160```javascript theme={null}160```javascript theme={null}
161const RISKY = /\brm\s+-rf?\b|\bgit\s+reset\s+--hard\b|\bgit\s+push\b.*--force/161const RISKY = /\brm\s+-rf?\b|\bgit\s+reset\s+--hard\b|\bgit\s+push\b.*--force/
162 162
163export function register(on) {163export function register(on) {
164 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {164 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
165 // 질문 없이 다른 모든 명령을 통과시킴165 // Let every other command through without a question
166 if (!RISKY.test(e.command)) return next(e)166 if (!RISKY.test(e.command)) return next(e)
167 // 안전한 답변에서 시작하므로 아무도 답변하지 않는 질문은 명령을 거부167 // Start from the safe answer, so a question nobody answers refuses the command
168 let answer = 'Refuse'168 let answer = 'Refuse'
169 try {169 try {
170 // 도구 호출은 사용자가 두 레이블 중 하나를 선택할 때까지 여기서 기다림170 // The tool call waits here until the user picks one of the two labels
171 answer = await $.ui.ask('Run this command? ' + e.command, ['Run it', 'Refuse'])171 answer = await $.ui.ask('Run this command? ' + e.command, ['Run it', 'Refuse'])
172 } catch {172 } catch {
173 // 사용자가 질문을 해제했거나 이것이 아무도 물어볼 사람이 없는 claude -p 실행173 // The user dismissed the question, or this is a claude -p run with nobody to ask
174 }174 }
175 if (answer !== 'Run it') {175 if (answer !== 'Run it') {
176 // next를 호출하지 않고 응답하므로 명령이 실행되지 않음176 // Answer without calling next, so the command doesn't run
177 return { deny: 'The user declined this command. Ask before trying a different approach.' }177 return { deny: 'The user declined this command. Ask before trying a different approach.' }
178 }178 }
179 return next(e)179 return next(e)
181}181}
182```182```
183 183
184Claude가 `rm -rf build`와 같은 명령을 시도할 때 질문이 명령과 함께 나타나고 명령은 답변을 기다립니다:184Claude가 `rm -rf build` 같은 명령을 시도하면 해당 명령이 포함된 질문이 표시되고, 명령은 답변을 기다립니다.
185 185
186* **사용자가 Run it을 선택**: 훅이 `next(e)`를 호출하고 일반적인 권한 확인이 여전히 그 후에 실행됩니다186* **사용자가 Run it을 선택하는 경우**: 훅이 `next(e)`를 호출하며, 그 후에도 일반적인 권한 검사가 계속 실행됩니다
187* **사용자가 Refuse를 선택**: 명령이 실행되지 않으며 Claude는 `deny` 텍스트를 읽습니다187* **사용자가 Refuse를 선택하는 경우**: 명령이 실행되지 않으며, Claude는 `deny` 텍스트를 읽습니다
188* **사용자가 답변을 입력**: `$.ui.ask`는 입력된 텍스트로 해결됩니다. 훅은 `Run it`과 비교하므로 다른 텍스트는 명령을 거부합니다.188* **사용자가 답변을 입력하는 경우**: `$.ui.ask`가 입력된 텍스트로 resolve됩니다. 훅은 이를 `Run it`과 비교하므로 그 외의 텍스트는 명령을 거부합니다.
189* **아무도 답변하지 않음**: `$.ui.ask`는 사용자가 질문을 해제하거나 **Chat about this**를 선택할 때 또는 `claude -p` 실행에서 거부하므로 `catch` 블록은 답변을 `Refuse`로 유지합니다189* **아무도 답변하지 않는 경우**: 사용자가 질문을 닫거나 **Chat about this**를 선택하면, 그리고 `claude -p` 실행에서는 `$.ui.ask`가 reject되므로 `catch` 블록이 답변을 `Refuse`로 유지합니다
190 190
191`$.ui.ask`와 같은 mods API 호출 내에서 대기를 유지하세요. 이 시간은 훅의 [10초 시간 제한](/docs/ko/plugins/mods/reference#limits)에 포함되지 않기 때문입니다. 자신의 약속을 기다리는 데 소비된 시간은 포함됩니다. Claude Code는 시간 초과된 훅을 건너뛰므로 보류된 명령이 실행됩니다.191대기는 `$.ui.ask` 같은 mods API 호출 안에서 이루어지도록 해야 합니다. 그 시간은 훅의 [시간 제한](/docs/ko/plugins/mods/reference#limits)에 포함되지 않기 때문입니다. 직접 만든 promise를 기다리는 데 소요된 시간은 포함됩니다. Claude Code는 시간 초과된 훅을 건너뛰므로, 보류된 명령이 실행됩니다.
192
193<h4 id="approve-or-refuse-a-tool-call-before-the-user-is-asked">
194 사용자에게 묻기 전에 도구 호출 승인 또는 거부하기
195</h4>
196
197도구 호출의 실행 가능 여부를 결정하려면 Claude Code가 그 결정을 내리는 이벤트인 [`tool.check`](/docs/ko/plugins/mods/reference#tools)를 처리합니다. 이 이벤트는 권한 규칙과 설정 훅이 결정을 내린 후에 발생하며, `next(e)`는 그 결정인 `allow`, `ask`, `deny` 중 하나로 resolve됩니다. 훅은 그 결정이나 다른 결정을 반환합니다. `e.input`에는 Bash의 `command`처럼 도구의 인수가 들어 있습니다.
198
199고정된 명령이나 경로에는 코드가 필요 없는 `Bash(npm test)` 같은 [권한 규칙](/docs/ko/permissions#permission-rule-syntax)을 사용합니다. 현재 Git 브랜치나 다른 훅이 기록한 값처럼 그 시점의 상태에 따라 결정이 달라지는 경우에 `tool.check`를 처리합니다.
200
201다음 훅은 현재 브랜치가 `main`일 때 `git push`를 거부합니다.
202
203```javascript theme={null}
204on('tool.check', { tool: 'Bash' }, async ($, e, next) => {
205 // What the permission rules and settings hooks decided: 'allow', 'ask', or 'deny'
206 const decided = await next(e)
207 if (!e.input.command.includes('git push')) return decided
208 const branch = await $.process.run(['git', 'branch', '--show-current'])
209 if (branch.stdout.trim() !== 'main') return decided
210 return { decision: 'deny', reason: 'Push from a branch other than main' }
211})
212```
213
214`main`에서는 규칙이 `git push`를 허용하더라도 훅이 `deny`를 반환합니다. 다른 브랜치에서, 그리고 다른 명령에 대해서는 mod가 없을 때와 동일한 결정이 적용됩니다.
215
216이 훅은 명령의 텍스트를 대조하므로 Claude를 위한 알림 정도로 취급해야 합니다. 모든 사람의 `main` 푸시를 차단하려면 Git 호스트에서 브랜치를 보호하세요.
217
218훅은 `allow`, `ask`, `deny`를 반환할 수 있으므로, 관리형 설정 외부의 `PreToolUse` 훅이 차단한 호출을 승인할 수도 있습니다. [훅으로 권한 확장하기](/docs/ko/permissions#extend-permissions-with-hooks)에서 mod보다 우선하는 결정을 확인할 수 있습니다.
192 219
193<h3 id="rewrite-or-add-to-a-prompt">220<h3 id="rewrite-or-add-to-a-prompt">
194 프롬프트 재작성 또는 추가하기221 프롬프트 다시 작성하거나 내용 추가하기
195</h3>222</h3>
196 223
197`prompt.submit` 훅은 턴이 시작되기 전에 각 프롬프트를 보므로 텍스트를 재작성하거나 추가할 수 있습니다. `e.text`는 입력된 것입니다.224`prompt.submit` 훅은 턴이 시작되기 전에 각 프롬프트를 확인하므로 텍스트를 다시 작성하거나 내용을 추가할 수 있습니다. `e.text`는 입력된 내용입니다.
198 225
199| 이것을 하려면 | 이것을 반환하세요 |226| 수행할 작업 | 반환할 값 |
200| :- | :- |227| :- | :- |
201| 프롬프트를 재작성합니다. 트랜스크립트의 메시지는 새 텍스트를 표시합니다. | `next({ ...e, text: newText })` |228| 프롬프트를 다시 작성합니다. 트랜스크립트의 메시지에 새 텍스트가 표시됩니다. | `next({ ...e, text: newText })` |
202| Claude만 읽는 텍스트를 프롬프트 후에 추가합니다 | `next({ ...e, context: [...(e.context ?? []), extraText] })` |229| 프롬프트 뒤에 Claude만 읽는 텍스트를 추가합니다 | `next({ ...e, context: [...(e.context ?? []), extraText] })` |
203| 프롬프트가 전송되지 않도록 중지합니다 | `{ drop: 'the reason' }` |230| 프롬프트가 전송되지 않도록 합니다 | `{ drop: 'the reason' }` |
204 231
205이 훅은 프롬프트가 풀 요청을 언급할 때마다 현재 브랜치 이름을 Claude에게 추가합니다:232다음 훅은 프롬프트에 풀 리퀘스트가 언급될 때마다 Claude를 위해 현재 브랜치 이름을 추가합니다.
206 233
207```javascript theme={null}234```javascript theme={null}
208on('prompt.submit', async ($, e, next) => {235on('prompt.submit', async ($, e, next) => {
209 // 풀 요청을 언급하지 않는 프롬프트를 그대로 전달236 // Pass on a prompt that doesn't mention a pull request as it is
210 if (!/\bPR\b|pull request/i.test(e.text)) return next(e)237 if (!/\bPR\b|pull request/i.test(e.text)) return next(e)
211 const git = await $.process.run(['git', 'branch', '--show-current'])238 const git = await $.process.run(['git', 'branch', '--show-current'])
212 // git 저장소 외부에서 명령이 실패하므로 추가할 브랜치가 없음239 // Outside a git repository the command fails, so there's no branch to add
213 if (git.exitCode !== 0) return next(e)240 if (git.exitCode !== 0) return next(e)
214 // 이전 훅이 추가한 모든 컨텍스트를 유지하고 Claude를 위해 하나 더 추가241 // Keep any context an earlier hook added, and add one more line for Claude
215 return next({ ...e, context: [...(e.context ?? []), 'Current branch: ' + git.stdout.trim()] })242 return next({ ...e, context: [...(e.context ?? []), 'Current branch: ' + git.stdout.trim()] })
216})243})
217```244```
218 245
219`open a PR for this change`와 같은 프롬프트를 보낼 때 메시지는 트랜스크립트에서 동일하게 보이며, Claude는 `Current branch: feature/auth`와 같은 줄도 그 후에 읽습니다. 풀 요청을 언급하지 않는 프롬프트는 변경되지 않고 통과하며 `git`은 실행되지 않습니다.246`open a PR for this change` 같은 프롬프트를 보내면 트랜스크립트의 메시지는 그대로 보이며, Claude는 그 뒤에 `Current branch: feature/auth` 같은 줄도 읽습니다. 풀 리퀘스트를 언급하지 않는 프롬프트는 변경 없이 전달되며 `git`도 실행되지 않습니다.
220 247
221[다른 이벤트](/docs/ko/plugins/mods/reference#prompts-and-what-claude-reads)는 Claude가 읽는 나머지를 다룹니다: 시스템 프롬프트의 각 섹션에 대한 `prompt.section`, 첫 번째 메시지와 함께 전송되는 컨텍스트에 대한 `prompt.context`, 그리고 스킬의 텍스트에 대한 `skill.prompt`. 이 훅의 텍스트가 요청 간에 변경되면 [프롬프트 캐시를 무효화합니다](/docs/ko/prompt-caching).248[다른 이벤트](/docs/ko/plugins/mods/reference#prompts-and-what-claude-reads)는 Claude가 읽는 나머지 내용을 다룹니다. 시스템 프롬프트의 각 섹션에는 `prompt.section`, 첫 번째 메시지와 함께 전송되는 컨텍스트에는 `prompt.context`, 스킬의 텍스트에는 `skill.prompt`를 사용합니다. 이러한 훅에서 나온 텍스트가 요청마다 달라지면 [프롬프트 캐시가 무효화됩니다](/docs/ko/prompt-caching).
222 249
223<h3 id="follow-a-turn">250<h3 id="follow-a-turn">
224 턴 따라가기251 턴 추적하기
225</h3>252</h3>
226 253
227턴은 Claude가 하나의 프롬프트에 응답하여 수행하는 모든 것입니다. `turn.start`, `turn.step`, 그리고 `turn.complete`를 훅하여 하나를 따라가세요:254턴은 하나의 프롬프트에 응답하여 Claude가 수행하는 모든 작업입니다. 턴을 추적하려면 `turn.start`, `turn.step`, `turn.complete`를 처리합니다.
228 255
229| 이벤트 | 언제 발생하는가 | 훅이 할 수 있는 것 |256| 이벤트 | 발생 시점 | 훅이 할 수 있는 작업 |
230| :- | :- | :- |257| :- | :- | :- |
231| `turn.start` | 턴이 시작됩니다 | 관찰합니다. `e.turnId`는 다른 두 이벤트에서 턴을 식별합니다. |258| `turn.start` | 턴이 시작될 때 | 관찰합니다. `e.turnId`는 나머지 두 이벤트에서 해당 턴을 식별합니다. |
232| `turn.step` | Claude Code가 모델에 하나의 요청을 보내려고 합니다. 도구 호출이 있는 턴은 여러 개를 가집니다. `e.agentId`는 서브에이전트의 요청에 대해 설정됩니다. | 각 요청의 토큰 사용량을 읽고, `next({ ...e, model })`로 다른 모델에 보내거나, 모델을 호출하지 않고 응답합니다 |259| `turn.step` | Claude Code가 모델에 요청 하나를 보내려 할 때입니다. 도구 호출이 있는 턴에는 요청이 여러 개 있습니다. 서브에이전트의 요청에는 `e.agentId`가 설정됩니다. | 각 요청의 토큰 사용량을 읽거나, `next({ ...e, model })`로 다른 모델에 보내거나, 모델을 호출하지 않고 응답합니다 |
233| `turn.complete` | 턴이 끝났으며, 사용자가 중단한 턴을 포함하며, 여기서 `e.isAborted`는 `true`입니다. `e.answer`는 Claude의 최종 텍스트이고, `e.durationMs`는 소요된 시간이며, `e.usage`는 턴의 토큰 합계입니다. 서브에이전트의 턴은 `e.agentId`가 설정된 상태로 발생합니다. | 관찰하거나, `{ text: 'Done in 12 seconds' }`와 같은 `text` 필드가 있는 객체를 반환하여 답변 아래에 줄을 표시합니다 |260| `turn.complete` | 턴이 종료되었을 때이며, 사용자가 중단한 턴도 포함됩니다. 이 경우 `e.isAborted`는 `true`입니다. `e.answer`는 Claude의 최종 텍스트, `e.durationMs`는 소요 시간, `e.usage`는 턴의 토큰 합계입니다. 서브에이전트의 턴에서는 `e.agentId`가 설정된 상태로 발생합니다. | 관찰하거나, `{ text: 'Done in 12 seconds' }`처럼 `text` 필드가 있는 객체를 반환하여 답변 아래에 한 줄을 표시합니다 |
234 261
235`turn.step` 훅을 비동기 생성기로 작성하세요. 이벤트가 스트림되기 때문입니다. `yield* next(e)`는 응답을 스트림할 때 전달하고 완료된 결과로 평가됩니다. 이 훅은 각 요청에서 Claude API가 [프롬프트 캐시](/docs/ko/prompt-caching)에서 제공한 양을 기록합니다:262이 이벤트는 스트리밍되므로 `turn.step` 훅은 async generator로 작성합니다. `yield* next(e)`는 응답을 스트리밍되는 대로 전달하고 완료된 결과로 평가됩니다. 다음 훅은 각 요청 중 Claude API가 [프롬프트 캐시](/docs/ko/prompt-caching)에서 제공한 양을 로그에 기록합니다.
236 263
237```javascript theme={null}264```javascript theme={null}
238// function*는 훅을 생성기로 만들어 응답을 조각별로 전달할 수 있음265// function* makes the hook a generator, which can pass the response on piece by piece
239on('turn.step', async function* ($, e, next) {266on('turn.step', async function* ($, e, next) {
240 // 요청을 보내고, 각 조각이 도착할 때 전달하고, 완료된 결과를 유지267 // Send the request, forward each piece as it arrives, and keep the finished result
241 const result = yield* next(e)268 const result = yield* next(e)
242 // 토큰 개수를 보고하지 않는 결과를 건너뜀269 // Skip a result that reports no token counts
243 if (result.usage) {270 if (result.usage) {
244 $.ui.log('cache read ' + result.usage.cache_read_input_tokens + ' · wrote ' + result.usage.cache_creation_input_tokens)271 $.ui.log('cache read ' + result.usage.cache_read_input_tokens + ' · wrote ' + result.usage.cache_creation_input_tokens)
245 }272 }
246 // 결과를 변경하지 않고 반환하므로 턴이 평소대로 계속됨273 // Return the result unchanged, so the turn continues as usual
247 return result274 return result
248})275})
249```276```
250 277
251Claude의 응답은 모드 없이 하는 것처럼 화면으로 스트림됩니다. 각 요청이 완료된 후 트랜스크립트의 흐릿한 줄이 캐시에서 읽은 토큰 수와 쓴 토큰 수를 제공합니다. 도구 호출이 있는 턴은 여러 요청을 가지므로 여러 줄을 추가합니다.278Claude의 응답은 mod가 없을 때와 동일하게 화면에 스트리밍됩니다. 각 요청이 완료되면 트랜스크립트의 흐린 줄에 캐시에서 읽은 토큰 수와 캐시에 기록된 토큰 수가 표시됩니다. 도구 호출이 있는 턴에는 요청이 여러 개 있으므로 여러 줄이 추가됩니다.
252 279
253`result.usage`는 Claude API가 요청에 대해 보고하는 네 가지 토큰 개수와 응답한 `model`을 보유합니다: `input_tokens`, `output_tokens`, `cache_read_input_tokens`, 그리고 `cache_creation_input_tokens`. 훅은 서브에이전트의 요청에 대해서도 실행되므로 주 대화만 원할 때 `e.agentId`를 확인하세요.280`result.usage`에는 Claude API가 요청에 대해 보고하는 토큰 수인 `input_tokens`, `output_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`와 응답한 `model`이 들어 있습니다. 이 훅은 서브에이전트의 요청에도 실행되므로, 메인 대화만 원하는 경우 `e.agentId`를 확인하세요.
254 281
255<h3 id="hook-the-settings-hook-events">282<h3 id="hook-the-settings-hook-events">
256 설정 훅 이벤트 훅하기283 설정 훅 이벤트 처리하기
257</h3>284</h3>
258 285
259설정 훅은 설정 파일에서 구성하는 명령, HTTP, 프롬프트, 그리고 에이전트 훅입니다. 각 [설정 훅 이벤트](/docs/ko/hooks#hook-events), 예를 들어 `Stop`, `SessionEnd`, 또는 `PostToolUse`는 또한 `classic.` 다음에 설정 훅 이벤트의 이름이 오는 이벤트입니다. 예를 들어 `classic.Stop`. `e`는 설정 훅이 stdin에서 받는 JSON이며, `transcript_path`를 포함합니다.286설정 훅은 설정 파일에서 구성하는 command, HTTP, prompt, agent 훅입니다. `Stop`, `SessionEnd`, `PostToolUse` 같은 각 [설정 훅 이벤트](/docs/ko/hooks#hook-events)는 `classic.` 뒤에 설정 훅 이벤트 이름이 붙은 이벤트(예: `classic.Stop`)이기도 합니다. `e`는 설정 훅이 stdin으로 받는 JSON이며, `transcript_path`를 포함합니다.
260 287
261이 훅은 Claude가 응답을 마칠 때 발생하는 `Stop`을 사용하여 세션의 트랜스크립트가 저장되는 위치를 기록합니다:288다음 훅은 Claude가 응답을 마칠 때 발생하는 `Stop`을 사용하여 세션의 트랜스크립트가 저장된 위치를 로그에 기록합니다.
262 289
263```javascript theme={null}290```javascript theme={null}
264on('classic.Stop', async ($, e, next) => {291on('classic.Stop', async ($, e, next) => {
265 // e는 설정 파일의 Stop 훅이 stdin에서 읽는 것과 같은 필드를 가짐292 // e has the same fields a Stop hook in a settings file reads from stdin
266 $.ui.log('Transcript saved at ' + e.transcript_path)293 $.ui.log('Transcript saved at ' + e.transcript_path)
267 // 이벤트를 전달하므로 설정 파일의 Stop 훅이 여전히 실행됨294 // Pass the event on, so Stop hooks in your settings files still run
268 return next(e)295 return next(e)
269})296})
270```297```
271 298
272Claude가 응답을 마칠 때마다 트랜스크립트의 흐릿한 줄이 트랜스크립트 파일의 경로를 제공합니다. 훅은 `next(e)`를 반환하므로 이벤트를 관찰하고 턴이 끝나는 방식에 대해 아무것도 변경하지 않습니다.299Claude가 응답을 마칠 때마다 트랜스크립트의 흐린 줄에 트랜스크립트 파일의 경로가 표시됩니다. 훅이 `next(e)`를 반환하므로 이벤트를 관찰만 하며 턴이 종료되는 방식은 전혀 변경하지 않습니다.
273 300
274<h2 id="run-alongside-other-mods">301<h2 id="run-alongside-other-mods">
275 다른 모드와 함께 실행302 다른 mod와 함께 실행하기
276</h2>303</h2>
277 304
278여러 모드가 동일한 이벤트를 후킹할 수 있으며, 그 중 하나가 실패할 수 있습니다. 모드가 도구 호출을 차단하는 경우, 체인에서의 위치와 후크가 실패할 때 발생하는 상황을 확인하십시오.305여러 mod가 같은 이벤트를 처리할 수 있으며, 그중 어느 것이든 실패할 수 있습니다. mod가 도구 호출을 차단한다면 체인에서 해당 mod의 위치와 훅이 실패할 때 어떤 일이 일어나는지 확인하십시오.
279 306
280<h3 id="the-order-mods-run-in">307<h3 id="the-order-mods-run-in">
281 모드가 실행되는 순서308 mod가 실행되는 순서
282</h3>309</h3>
283 310
284동일한 이벤트의 후킹은 하나의 미들웨어 체인을 형성합니다. 각 모드의 `next`는 다음 모드의 후크를 호출하고, 마지막 `next`는 Claude Code의 자체 동작에 도달합니다. 첫 번째 모드는 가장 바깥쪽입니다. 즉, 다른 모드보다 먼저 이벤트를 보고 그 후에 결과를 보며, 다른 모드가 실행될지 여부를 결정합니다. 나중의 모드는 이전 모드가 이벤트를 보는 것을 막을 수 없습니다.311같은 이벤트에 대한 훅은 하나의 미들웨어 체인을 형성합니다. 각 mod의 `next`는 다음 mod의 훅을 호출하며, 마지막 `next`는 Claude Code 자체의 동작에 도달합니다. 첫 번째 mod가 가장 바깥쪽에 있습니다. 이 mod는 다른 mod보다 먼저 이벤트를 보고 다른 mod보다 나중에 결과를 보며, 다른 mod의 실행 여부를 결정합니다. 뒤에 있는 mod는 앞에 있는 mod가 이벤트를 보는 것을 막을 수 없습니다.
285 312
286Claude Code는 각 모드의 출처에 따라 체인을 정렬합니다.313Claude Code는 각 mod의 출처에 따라 체인의 순서를 정합니다.
287 314
2881. 내장 가드 `sec-default@builtin`은 Claude Code에 내장된 모드로, `/plugin`에서 `cc-plugin-sec-default`로 나열되며, [여기서 로드](/docs/ko/plugins/mods/admin#know-what-happens-by-default)되고, 조직이 [`prependPlugins`](/docs/ko/plugins/mods/admin#install-your-organizations-mods)에 나열한 모드, 그리고 조직의 것으로 간주되며 `appendPlugins`에 없는 다른 모드3151. 기본 제공 가드 `sec-default@builtin`([로드되는 경우](/docs/ko/plugins/mods/admin#know-what-happens-by-default), `/plugin`에서 `cc-plugin-sec-default`로 표시되는 Claude Code 내장 mod), 조직이 [`prependPlugins`](/docs/ko/plugins/mods/admin#install-your-organizations-mods)에 나열한 mod, 그리고 조직의 mod로 간주되면서 `appendPlugins`에 없는 그 밖의 mod
2892. 설치한 모드3162. 사용자가 설치한 mod
2903. 조직이 `appendPlugins`에 나열한 모드3173. 조직이 `appendPlugins`에 나열한 mod
2914. Claude Code에 내장된 다른 모드3184. Claude Code에 내장된 그 밖의 mod
292 319
293설치한 모드 중에서, 모드는 매니페스트의 `dependencies` 아래에 나열한 모드보다 먼저 실행됩니다. 하나의 모듈 내에서, 후킹은 `register`가 `on`을 호출한 순서대로 실행됩니다.320사용자가 설치한 mod 중에서는 mod가 매니페스트의 `dependencies` 아래에 나열한 mod보다 먼저 실행됩니다. 하나의 모듈 안에서는 `register`가 `on`을 호출한 순서대로 훅이 실행됩니다.
294 321
295<h4 id="where-settings-hooks-run-in-the-order">322<h4 id="where-settings-hooks-run-in-the-order">
296 설정 후킹이 순서대로 실행되는 위치323 설정 훅이 실행되는 순서상의 위치
297</h4>324</h4>
298 325
299설정 파일에서 구성된 `PreToolUse` 후킹도 도구 호출 중에 실행되며, 모드 체인의 고정된 지점에서 실행됩니다.326설정 파일에 구성된 `PreToolUse` 훅도 도구 호출 중에 mod 체인의 고정된 지점에서 실행됩니다.
300 327
301* **관리되는 설정의 `PreToolUse` 후킹**: 첫 번째 모드의 `tool.call` 후킹 전에 실행되며, 그 중 하나의 차단은 최종적이므로 모드가 호출을 보지 못합니다.328* **관리형 설정의 `PreToolUse` 훅**: 첫 번째 mod의 `tool.call` 훅보다 먼저 실행되며, 이 훅 중 하나가 차단하면 그 결정이 최종이므로 어떤 mod도 해당 호출을 보지 못합니다.
302* **다른 모든 설정 파일 및 플러그인의 `hooks/hooks.json`의 `PreToolUse` 후킹**: 마지막 모드가 `next`를 호출한 후, Claude Code의 자체 동작의 일부로 실행됩니다. `next`를 호출하지 않고 `tool.call`에 응답하는 모드는 이들이 실행되는 것을 방지하고, `next`를 호출하는 모드는 반환하는 결과에서 이들의 결정을 봅니다.329* **그 밖의 모든 설정 파일과 플러그인의 `hooks/hooks.json`에 있는 `PreToolUse` 훅**: Claude Code 자체 동작의 일부로서 마지막 mod가 `next`를 호출한 후에 실행됩니다. `next`를 호출하지 않고 `tool.call`에 응답하는 mod는 이 훅의 실행을 막으며, `next`를 호출하는 mod는 반환되는 결과에서 이 훅의 결정을 확인할 수 있습니다.
303 330
304[`tool.check`](/docs/ko/plugins/mods/reference#tools)는 Claude Code가 도구 호출 실행 여부를 결정하는 이벤트입니다. 이는 해당 후킹과 권한 규칙이 결정한 후에 발생하며, `next(e)`는 해당 결정으로 해석됩니다. `tool.check`의 후킹은 `{ decision: 'allow' }`와 같은 다른 결정을 반환할 수 있으므로, 두 번째 그룹의 후킹이 차단한 호출을 승인할 수 있습니다. [후킹으로 권한 확장](/docs/ko/permissions#extend-permissions-with-hooks)은 어떤 결정이 모드보다 우선하는지 나열합니다.331[`tool.check`](#approve-or-refuse-a-tool-call-before-the-user-is-asked)는 이러한 훅과 권한 규칙이 결정을 내린 후에 발생하므로, 여기에 연결된 훅은 두 번째 그룹의 훅이 차단한 호출을 승인할 수 있습니다.
305 332
306<h3 id="handle-a-hook-that-fails">333<h3 id="handle-a-hook-that-fails">
307 실패한 후킹 처리334 실패한 훅 처리하기
308</h3>335</h3>
309 336
310실패한 후킹은 세션을 중단하지 않으며, 대신 발생하는 상황을 결정할 수 있습니다. `.catch` 핸들러가 없는 후킹이 throw되거나, 시간 초과되거나, 잘못된 형태의 결과를 반환할 때, 다음에 발생하는 상황은 `next`를 호출했는지 여부에 따라 달라집니다.337훅이 실패해도 세션이 중단되지는 않으며, 대신 어떤 일이 일어날지 직접 결정할 수 있습니다. `.catch` 핸들러가 없는 훅이 예외를 던지거나, 시간 초과되거나, 잘못된 형태의 결과를 반환하면, 이후 동작은 해당 훅이 `next`를 호출했는지에 따라 달라집니다.
311 338
312* **`next`를 호출하기 전에 실패함**: Claude Code는 이를 건너뛰고, 다음 핸들러가 그 자리에서 실행됩니다.339* **`next`를 호출하기 전에 실패한 경우**: Claude Code는 해당 훅을 건너뛰고 그 자리에서 다음 핸들러를 실행합니다
313* **`next`가 해석된 후에 실패함**: 해당 결과가 유지되고, 아무것도 두 번 실행되지 않습니다.340* **`next`가 완료된 후에 실패한 경우**: 해당 결과가 그대로 유지되며, 어떤 것도 다시 실행되지 않습니다
314 341
315한 줄은 모드, 이벤트, 그리고 이유를 이름 지으며, 예를 들어 `my-mod: tool.call hook skipped: threw Error: boom`입니다. 이를 읽는 위치는 세션에 따라 달라지며, [모드가 아무것도 하지 않는 이유 알아내기](/docs/ko/plugins/mods/troubleshoot#find-out-why-a-mod-does-nothing)에 나열되어 있습니다. 그리기가 유효성 검사를 통과하지 못하는 `ui.render` 후킹은 [요소에서 트리 구축](/docs/ko/plugins/mods/interface#build-a-tree-from-elements)에서 설명하는 대로 다르게 보고됩니다.342mod, 이벤트, 이유를 나타내는 한 줄이 기록됩니다(예: `my-mod: tool.call hook skipped: threw Error: boom`). 이 내용을 확인하는 위치는 [mod가 아무 동작도 하지 않는 이유 찾기](/docs/ko/plugins/mods/troubleshoot#find-out-why-a-mod-does-nothing)에 나와 있듯이 세션에 따라 다릅니다. 그리기 결과가 유효성 검사를 통과하지 못한 `ui.render` 훅은 [요소로 트리 만들기](/docs/ko/plugins/mods/interface#build-a-tree-from-elements)에 설명된 대로 다른 방식으로 보고됩니다.
316 343
317호출을 차단하는 후킹이 실패하도록 닫히게 하려면, 그 자리에 응답하는 `.catch` 오류 핸들러를 추가하십시오. 여기서 `guard`는 후킹 함수입니다.344호출을 차단하는 훅이 실패 시 닫힌 상태(fail closed)가 되도록 하려면, 그 자리에서 대신 응답하는 `.catch` 오류 핸들러를 추가하십시오. 여기서 `guard`는 사용자의 훅 함수입니다.
318 345
319```javascript theme={null}346```javascript theme={null}
320// on은 등록을 반환하고, .catch는 해당 하나의 후킹에 핸들러를 첨부합니다.347// on returns a registration, and .catch attaches a handler to that one hook
321on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {348on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {
322 // next.error.kind는 'throw' 또는 'timeout'이며, guard가 어떻게 실패했는지를 나타냅니다.349 // next.error.kind is 'throw' or 'timeout', which says how guard failed
323 return { deny: 'The command guard failed, so this command was not run: ' + next.error.kind }350 return { deny: 'The command guard failed, so this command was not run: ' + next.error.kind }
324})351})
325```352```
326 353
327`guard`가 작동하는 동안, 핸들러는 실행되지 않습니다. `guard`가 Bash 호출에서 throw되거나 시간 초과될 때, Claude Code는 동일한 이벤트로 핸들러를 호출합니다. 핸들러는 `{ deny }`를 반환하므로, 명령이 실행되지 않으며, Claude는 끝에 `throw` 또는 `timeout`이 있는 텍스트를 읽습니다. 핸들러가 없으면, Claude Code는 `guard`를 건너뛰고 명령을 실행합니다. 핸들러는 [1초](/docs/ko/plugins/mods/reference#limits) 내에 응답해야 합니다.354`guard`가 정상적으로 작동하는 동안에는 핸들러가 실행되지 않습니다. `guard`가 Bash 호출에서 예외를 던지거나 시간 초과되면, Claude Code는 같은 이벤트로 핸들러를 호출합니다. 핸들러가 `{ deny }`를 반환하므로 명령은 실행되지 않으며, Claude는 끝에 `throw` 또는 `timeout`이 붙은 텍스트를 읽습니다. 핸들러가 없다면 Claude Code는 `guard`를 건너뛰고 명령을 실행합니다. 핸들러에는 자체적으로 더 짧은 [시간 제한](/docs/ko/plugins/mods/reference#limits)이 적용됩니다.
328 355
329<h2 id="next-steps">356<h2 id="next-steps">
330 다음 단계357 다음 단계
331</h2>358</h2>
332 359
333* [mods API 사용](/docs/ko/plugins/mods/api): 명령과 도구를 추가하고, 모델을 호출하고, 타이머에서 작업을 실행합니다360* [mods API 사용하기](/docs/ko/plugins/mods/api): 명령과 도구를 추가하고, 모델을 호출하고, 타이머로 작업을 실행합니다
334* [인터페이스에 그리기](/docs/ko/plugins/mods/interface): 훅이 수집한 것을 창이나 프롬프트 위에 표시합니다361* [인터페이스에 그리기](/docs/ko/plugins/mods/interface): 훅이 수집한 내용을 창이나 프롬프트 위에 표시합니다
335* [모드 테스트](/docs/ko/plugins/mods/test): 테스트에서 이 이벤트 중 하나를 발생시킵니다362* [mod 테스트하기](/docs/ko/plugins/mods/test): 테스트에서 이러한 이벤트를 발생시킵니다
336* [Mods 참조](/docs/ko/plugins/mods/reference): 모든 이벤트, 모든 mods API 메서드, 그리고 제한363* [Mods 레퍼런스](/docs/ko/plugins/mods/reference): 모든 이벤트, 모든 mods API 메서드, 그리고 제한 사항을 다룹니다