Agent SDK 훅으로 에이전트 동작 가로채기
기준일: 2026-07-26 · CLI 기준 2.1.220
개요
훅은 에이전트 수명주기 이벤트에 개입해 도구 호출을 감사·차단·변환하고, 컨텍스트를 주입하며, 알림을 보낼 수 있습니다. SDK에서는 프로그램 콜백과 settings.json 파일시스템 훅이 함께 동작합니다.
핵심 개념
- Hook event:
PreToolUse,PostToolUse,Stop등 시점 - Matcher: 도구명 패턴으로 콜백 필터
- Callback: 입력 수신 →
HookJSONOutput반환 ({}= 허용, deny 구조 = 차단) - Python vs TypeScript: TypeScript가 더 많은 이벤트 지원
상세
동작 방식
도구 호출 전후 등에 등록된 콜백이 실행됩니다. PreToolUse에서:
{}— 진행 허용hookSpecificOutput.permissionDecision: "deny"+permissionDecisionReason— 차단 (사유가 tool result로 Claude에 전달)- 입력 수정 필드로 tool_input 변경 가능
PreToolUse에서 최상위 decision/reason은 deprecated입니다.
사용 가능 훅 (요약)
| 이벤트 | Python | TypeScript | 트리거 |
|---|---|---|---|
PreToolUse |
Yes | Yes | 도구 요청 (차단·수정) |
PostToolUse |
Yes | Yes | 도구 결과 |
PostToolUseFailure |
Yes | Yes | 도구 실패 |
PostToolBatch |
No | Yes | 배치 도구 완료 |
UserPromptSubmit |
Yes | Yes | 사용자 프롬프트 제출 |
UserPromptExpansion |
No | Yes | 명령/MCP 프롬프트 확장 |
MessageDisplay |
No | Yes | 어시스턴트 표시 텍스트 |
Stop / StopFailure |
Yes / No | Yes | 정상/오류 종료 |
SubagentStart / SubagentStop |
Yes | Yes | 서브에이전트 시작·종료 |
PreCompact / PostCompact |
Yes / No | Yes | 컴팩션 전후 |
PermissionRequest / PermissionDenied |
Yes / No | Yes | 권한 대화·분류기 deny |
SessionStart / SessionEnd |
No | Yes | 세션 수명 |
Notification |
Yes | Yes | 상태 메시지 |
Setup, TeammateIdle, TaskCreated, TaskCompleted |
No | Yes | 설정·팀·태스크 |
Elicitation / ElicitationResult |
No | Yes | MCP 사용자 입력 |
ConfigChange, InstructionsLoaded |
No | Yes | 설정·지침 로드 |
WorktreeCreate / WorktreeRemove, CwdChanged, FileChanged |
No | Yes | 워크트리·cwd·파일 감시 |
설정
import { query, type HookInput, type HookJSONOutput } from "@anthropic-ai/claude-agent-sdk";
const auditBash = async (input: HookInput): Promise<HookJSONOutput> => {
if (input.hook_event_name !== "PreToolUse") return {};
const toolInput = input.tool_input as { command?: string };
if (toolInput.command?.includes("rm -rf")) {
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked",
},
};
}
return {};
};
for await (const message of query({
prompt: "Refactor the auth module",
options: {
settingSources: ["project"],
hooks: {
PreToolUse: [{ matcher: "Bash", hooks: [auditBash] }]
}
}
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher, ResultMessage
import asyncio
async def audit_bash(input_data, tool_use_id, context):
command = input_data.get("tool_input", {}).get("command", "")
if "rm -rf" in command:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked",
}
}
return {}
async def main():
async for message in query(
prompt="Refactor the auth module",
options=ClaudeAgentOptions(
setting_sources=["project"],
hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[audit_bash])]},
),
):
if isinstance(message, ResultMessage) and message.subtype == "success":
print(message.result)
asyncio.run(main())
Matcher
- 도구명 문자열/정규식 스타일 매칭 (예:
Bash,Edit|Write) - 빈 matcher는 해당 이벤트 전체에 적용
- 다중 도구 매처로 필터 범위 축소
예시 패턴
- 입력 수정: 경로 rewrite, 플래그 강제
- 컨텍스트 추가 + 차단: deny reason으로 정책 설명
- 특정 도구 auto-approve: PermissionRequest에서 allow
- 다중 훅 등록: 감사 + 보안 분리
- 서브에이전트 추적: SubagentStart/Stop +
agent_id - HTTP/Slack: Notification에서 외부 webhook
문제 해결
| 이슈 | 조치 |
|---|---|
| 훅 미발화 | 이벤트명·등록 위치·matcher 확인 |
| matcher 미필터 | 정확한 도구 문자열 사용 |
| timeout | 콜백 비동기 I/O 단축 |
| 예기치 않은 차단 | deny reason·다른 훅 우선순위 확인 |
| 수정 입력 미적용 | PreToolUse 반환 스키마 확인 |
| Python Session hooks 없음 | TypeScript 전용 이벤트 — 대안 설계 |
| 서브에이전트 권한 프롬프트 폭증 | permission mode·훅 정책 조정 |
| 재귀 루프 | 훅 안에서의 도구 호출/에이전트 spawn 주의 |
| systemMessage 미표시 | 출력 경로·이벤트 지원 여부 확인 |
체크리스트
- 필요 이벤트와 Python/TS 지원 표를 대조했다
- PreToolUse deny는
hookSpecificOutput형식을 쓴다 - matcher가 실제 도구명과 일치한다
- 서브에이전트에서도 훅이 fire됨을 반영했다
- timeout·재귀 루프 가능성을 점검했다