CPN 한국어 자습서 · Claude Code in Action
3 · 훅과 SDK
Implementing a hook
설계한 훅을 실제로 만듭니다. settings.local.json에 matcher(Read|Grep)와 command(node ./hooks/read_hook.js)를 채우고, read_hook.js에서 stdin의 JSON을 파싱해 file_path에 .env가 들어 있으면 process.exit(2)로 차단합니다.
Stephen Grider · 강사
우리 커스텀 훅을 완성해 봅시다. 전체 목표는 Claude가 .env 파일의 내용을 절대 읽지 못하게 막는 것입니다. 지난 영상에서 필요한 설정 옵션을 많이 이야기했으니, 이번에는 주로 구현에 집중하겠습니다.
시작으로 .claude 디렉터리 안의 settings.local.json 파일을 엽니다. 여기엔 PreToolUse 훅과 PostToolUse 훅 목록이 있습니다. 조금 전 이야기한 대로, 우리는 Claude가 그 특정 파일을 읽지 못하게 막으려 하므로 PreToolUse 훅을 만듭니다. 타이핑을 줄이려고 설정 골격은 미리 넣어 두었습니다. 우리가 채울 건 matcher와 command뿐입니다.
먼저 matcher입니다. matcher에는 감시할 도구들을 적습니다. 우리 경우엔 앞서 정한 대로 Read와 Grep 도구 호출을 감시합니다. 두 도구 이름은 파이프(|) 기호로 구분합니다 — 소문자 L이나 대문자 I가 아니라, 키보드 엔터 키 위에 있는 그 기호입니다.
다음으로 그 두 도구를 호출하려 할 때 실행할 command를 적습니다. 여기엔 원하는 어떤 명령이든 넣을 수 있습니다 — CLI든 셸 스크립트 호출이든 무엇이든요. 이 파일의 나머지 패턴을 따라, 저는 미리 hooks 디렉터리에 넣어 둔 Node.js 스크립트를 호출하겠습니다. 그 안에 read_hook.js 파일을 만들어 두었고, 두 도구 중 하나를 호출하려 할 때 이 파일이 실행되길 원합니다.
그래서 자리표시자로 둔 true를 node ./hooks/read_hook.js로 바꿉니다. 이 파일을 저장하면 여기서 할 일은 끝입니다.
이제 Claude가 Read나 Grep 도구를 호출할 때 실제로 돌아갈 명령, 즉 read_hook.js를 구현합니다. 파일 맨 위에는 stdin에서 읽어 그 데이터를 JSON으로 파싱하는 코드가 이미 있습니다. 이 toolArgs 객체가 바로 다이어그램에서 보여 준 큰 JSON 객체입니다 — session ID, 도구 이름, 도구 입력 같은 속성이 들어 있죠.
우리가 할 일은 그 file_path를 보고, .env 파일을 읽으려는지 판단하는 것뿐입니다. 맞다면 종료 코드 2로 프로그램을 끝내고, "미안하지만 그 파일은 읽을 수 없다"는 정보를 Claude에 로그로 남깁니다. 코드에는 그 file_path를 읽는 부분이 이미 있고, toolInput.path를 보는 폴백도 있습니다 — 이유는 잠시 뒤에 설명합니다.
이제 to-do를 채웁니다. if (readPath.includes('.env'))면 Claude가 .env 파일을 읽으려는 것이니, 그 작업을 막고 Claude에 로그 피드백을 줍니다. 먼저 console.error를 넣습니다 — 표준 에러로 로그를 남겨야 그게 Claude 피드백이 되기 때문에 굳이 console.error를 씁니다. "You cannot read the .env file" 같은 메시지를 적고, process.exit(2)를 호출합니다.
테스트해 봅시다. 파일을 저장하고 Claude Code를 엽니다. 이미 열려 있다면 반드시 재시작하세요. 훅 변경을 적용하려면 Claude Code를 다시 시작해야 합니다. Claude에 .env 파일을 읽어 달라고 하면, 시도는 하겠지만 "You cannot read the .env file"이라는 에러가 돌아갑니다. Claude는 자신이 read 훅에 막혔다는 것까지 알아챕니다.
이 훅은 grep 작업에도 동작해야 합니다. 그래서 Claude에 grep 도구를 시켜 보면, 이것도 마찬가지로 금지됩니다. 이렇게 동작하는 훅을 하나 완성했습니다. 다만 이 훅은 그리 유용하진 않습니다 — 잠시 뒤 훨씬 더 유용한 훅을 보여 드리겠습니다.
이 장에서 배우는 것What you'll learn
약 4분settings.local.json에 matcher·command 채우기
matcher는 Read|Grep — 파이프(|)로 구분
command는 node ./hooks/read_hook.js
스크립트: stdin JSON 파싱 → file_path 확인
.env 포함 시 console.error + exit(2)
훅 변경 후엔 Claude Code 재시작 필수
.claude 디렉터리 안. 나에게만 적용되는 개인 설정 (훅 정의 포함).Read|Grep = Read 또는 Grep..claude/settings.local.json을 엽니다. 골격은 준비돼 있고, 우리가 채울 건 둘뿐입니다 — 감시할 도구를 적는 matcher(Read|Grep)와, 실행할 command(node ./hooks/read_hook.js)입니다.
// .claude/settings.local.json { "hooks": { "PreToolUse": [ { "matcher": "Read|Grep", // 파이프(|)로 구분 "hooks": [ { "type": "command", "command": "node ./hooks/read_hook.js" // true 자리표시자를 교체 } ] } ] } }
두 도구는 파이프 기호 |로 구분합니다 — 소문자 L이나 대문자 I가 아니라, 엔터 키 위에 있는 그 기호입니다. command 자리의 true 자리표시자를 node 호출로 바꿉니다.
이제 Read·Grep 호출 때 실제로 돌아갈 스크립트를 구현합니다. 맨 위는 stdin을 읽어 JSON으로 파싱하는 코드입니다. 우리가 더할 부분은 file_path에 .env가 들어 있는지 보고, 그렇다면 막는 로직입니다.
// hooks/read_hook.js const input = fs.readFileSync(0, "utf-8"); // 0 = stdin const toolArgs = JSON.parse(input); // 도구 호출 데이터 // file_path를 읽되, 없으면 path로 폴백 const readPath = toolArgs.tool_input?.file_path || toolArgs.tool_input?.path || ""; if (readPath.includes(".env")) { // stderr로 남겨야 Claude 피드백이 된다 console.error("You cannot read the .env file"); process.exit(2); // 2 = 차단 } process.exit(0); // 0 = 허용
file_path가 없을 때를 대비해 path로 폴백합니다 (Grep 등 도구마다 키가 다를 수 있음).console.error로 — stderr여야 Claude 피드백이 됩니다.process.exit(2)가 차단 신호, exit(0)은 통과입니다.저장한 뒤 Claude Code를 재시작합니다 — 훅 변경은 재시작해야 적용됩니다. 그리고 .env를 읽어 달라고 하면 차단되고, grep으로 시도해도 같은 이유로 막힙니다.
> .env 파일 읽어줘 ● Read(.env) ┗ PreToolUse:Read 훅에 의해 차단됨 You cannot read the .env file ● 죄송합니다. read 훅이 .env 읽기를 막고 있어 이 파일은 열 수 없습니다. > 그럼 grep으로 .env 안을 찾아줘 ● Grep(.env) ┗ 마찬가지로 차단됨 — You cannot read the .env file
칩을 눌러 다른 file_path로 훅이 어떻게 판단하는지 보세요. .env가 경로에 들어 있으면 exit(2)로 막습니다.
settings.local.json의 PreToolUse에 Read|Grep과 node ./hooks/read_hook.js.
stdin JSON 파싱 → file_path(폴백 path) 확인.
.env 포함이면 console.error + process.exit(2).
Claude Code 재시작 → .env 읽기·grep 모두 차단 확인.
Read|Grep, command는 node ./hooks/read_hook.js.file_path를 확인한다..env 포함 시 console.error(stderr) + process.exit(2)로 차단.Q1matcher에서 Read와 Grep을 함께 감시하려면?
파이프 기호 |로 도구를 묶습니다. Read 또는 Grep 호출 시 훅이 돕니다.
Q2차단 메시지를 console.error로 출력하는 이유는?
exit(2)와 함께 stderr 로그가 Claude 피드백이 됩니다. 그래서 console.error를 씁니다.
Q3훅을 수정한 뒤 반드시 해야 하는 일은?
훅 변경은 재시작해야 적용됩니다. 이미 열려 있었다면 다시 시작하세요.
도구가 실행되기 직전, 훅이 호출 정보를 받아 허용할지 차단할지 판단하는 과정을 종료 코드로 그대로 따라가 봅니다.
// PreToolUse 훅 엔진 — Claude Code가 도구를 실행하기 "직전", 훅 명령이 그 도구 호출을
// stdin으로 JSON 받아 판단한다. 종료 코드로 신호한다: 0=허용, 2=차단(Pre 훅 전용).
// 차단 이유는 stderr(console.error)로 남겨야 Claude에 피드백으로 전달된다.
// 훅 설정 — settings.local.json의 PreToolUse 한 항목에 해당한다.
const hook = {
matcher: "Read|Grep", // 이 도구들에만 훅이 걸린다(파이프로 여러 개)
protect: [".env", ".pem", "id_rsa"], // 경로에 이 조각이 들어가면 차단
};
// 도구 호출 하나를 판단한다. { exit, stderr } 를 돌려준다.
function runHook(call) {
// matcher에 걸리지 않는 도구는 그대로 통과(훅이 관여하지 않음)
if (hook.matcher.split("|").indexOf(call.tool_name) === -1) {
return { exit: 0, stderr: "" };
}
// file_path가 표준이지만 도구마다 키가 달라 path로 폴백한다(예: Grep)
const target = call.file_path || call.path || "";
const hit = hook.protect.find(function (frag) { return target.indexOf(frag) >= 0; });
if (hit) {
// exit(2)가 차단 신호. 이유는 stderr로 남겨야 Claude 피드백이 된다.
return { exit: 2, stderr: "차단: " + target + " 는 보호 대상(" + hit + ")입니다." };
}
return { exit: 0, stderr: "" };
}
// ── 여기서부터 직접 고쳐 보세요 ──
// Claude Code가 실행하려는 도구 호출들(stdin으로 하나씩 흘러온다고 보면 됩니다).
const incoming = [
{ tool_name: "Read", file_path: "/app/src/main.ts" },
{ tool_name: "Read", file_path: "/app/.env" },
{ tool_name: "Grep", path: "/app/.env.local" }, // Grep은 file_path 대신 path
{ tool_name: "Write", file_path: "/app/.env" }, // matcher 밖 — 이 훅은 관여 안 함
];
incoming.forEach(function (call) {
const r = runHook(call);
const shown = call.file_path || call.path || "(경로 없음)";
console.log(call.tool_name.padEnd(6) + " " + shown);
console.log(" → " + (r.exit === 2 ? "차단(exit 2)" : "허용(exit 0)"));
if (r.stderr) console.log(" stderr → " + r.stderr);
});
동작하는 훅을 만들었지만 함정도 있습니다. 다음은 훅을 쓸 때 흔히 부딪히는 주의점입니다 — 특히 경로와 공유에 관한 것. → 훅 주의점
전 코스는 계속 무료입니다. 등록하면 이 코스의 남은 16개 레슨을 끝까지 읽을 수 있습니다.
이미 등록하셨다면 그때 쓰신 이메일을 넣어 주세요.