hook-developer by parcadei/continuous-claude-v3
npx skills add https://github.com/parcadei/continuous-claude-v3 --skill hook-developer开发 Claude Code 钩子的完整参考。使用本文档编写具有正确输入/输出模式的钩子。
| 钩子 | 触发时机 | 可阻止? | 主要用途 |
|---|---|---|---|
| PreToolUse | 工具执行前 | 是 | 阻止/修改工具调用 |
| PostToolUse | 工具完成后 | 部分 | 响应工具结果 |
| UserPromptSubmit | 用户发送提示时 | 是 | 验证/注入上下文 |
| PermissionRequest | 权限对话框显示时 | 是 | 自动批准/拒绝 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| SessionStart | 会话开始时 | 否 | 加载上下文,设置环境变量 |
| SessionEnd | 会话结束时 | 否 | 清理/保存状态 |
| Stop | 代理完成时 | 是 | 强制继续 |
| SubagentStart | 子代理生成时 | 否 | 模式协调 |
| SubagentStop | 子代理完成时 | 是 | 强制继续 |
| PreCompact | 压缩前 | 否 | 保存状态 |
| Notification | 发送通知时 | 否 | 自定义提醒 |
钩子类型选项: type: "command" (bash) 或 type: "prompt" (LLM 评估)
目的: 在工具执行前阻止或修改其执行。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "default|plan|acceptEdits|bypassPermissions",
"hook_event_name": "PreToolUse",
"tool_name": "string",
"tool_input": {
"file_path": "string",
"command": "string"
},
"tool_use_id": "string"
}
输出 (JSON):
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow|deny|ask",
"permissionDecisionReason": "string",
"updatedInput": {}
},
"continue": true,
"stopReason": "string",
"systemMessage": "string",
"suppressOutput": true
}
退出码 2: 阻止工具,stderr 显示给 Claude。
常用匹配器: Bash, Edit|Write, Read, Task, mcp__.*
目的: 响应工具执行结果,向 Claude 提供反馈。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PostToolUse",
"tool_name": "string",
"tool_input": {},
"tool_response": {
"filePath": "string",
"success": true,
"output": "string",
"exitCode": 0
},
"tool_use_id": "string"
}
关键: 响应字段是 tool_response,而不是 tool_result。
输出 (JSON):
{
"decision": "block",
"reason": "string",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "string"
},
"continue": true,
"stopReason": "string",
"suppressOutput": true
}
阻止: "decision": "block" 配合 "reason" 会提示 Claude 处理该问题。
常用匹配器: Edit|Write, Bash
目的: 验证用户提示,在 Claude 处理前注入上下文。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "UserPromptSubmit",
"prompt": "string"
}
输出 (纯文本):
任何 stdout 文本都会作为上下文添加到 Claude。
输出 (JSON):
{
"decision": "block",
"reason": "string",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "string"
}
}
阻止: "decision": "block" 会清除提示,仅向用户(而非 Claude)显示 "reason"。
退出码 2: 阻止提示,仅向用户显示 stderr。
目的: 自动处理权限对话框决策。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PermissionRequest",
"tool_name": "string",
"tool_input": {}
}
输出:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow|deny",
"updatedInput": {},
"message": "string",
"interrupt": false
}
}
}
目的: 初始化会话,加载上下文,设置环境变量。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SessionStart",
"source": "startup|resume|clear|compact"
}
环境变量: CLAUDE_ENV_FILE - 写入 export VAR=value 以持久化环境变量。
输出 (纯文本或 JSON):
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "string"
},
"suppressOutput": true
}
纯文本 stdout 将作为上下文添加。
目的: 清理、保存状态、记录会话。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SessionEnd",
"reason": "clear|logout|prompt_input_exit|other"
}
输出: 无法影响会话(已结束)。仅用于清理。
目的: 控制 Claude 何时停止,强制继续。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "Stop",
"stop_hook_active": false
}
关键: 检查 stop_hook_active: true 以防止无限循环!
输出:
{
"decision": "block",
"reason": "string"
}
阻止: "decision": "block" 强制 Claude 继续,并将 "reason" 作为提示。
目的: 当子代理(Task 工具)生成时运行。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SubagentStart",
"agent_id": "string"
}
输出: 仅上下文注入(无法阻止)。
目的: 控制子代理(Task 工具)何时停止。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SubagentStop",
"stop_hook_active": false
}
输出: 与 Stop 相同。
目的: 在上下文压缩前保存状态。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PreCompact",
"trigger": "manual|auto",
"custom_instructions": "string"
}
匹配器: manual, auto
输出:
{
"continue": true,
"systemMessage": "string"
}
目的: 自定义通知处理。
输入:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "Notification",
"message": "string",
"notification_type": "permission_prompt|idle_prompt|auth_success|elicitation_dialog"
}
匹配器: permission_prompt, idle_prompt, auth_success, elicitation_dialog, *
输出:
{
"continue": true,
"suppressOutput": true,
"systemMessage": "string"
}
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
"timeout": 60
}
]
}
]
}
}
| 模式 | 匹配项 |
|---|---|
Bash | 精确匹配 Bash 工具 |
| `Edit | Write` |
Read.* | 正则表达式:Read* |
mcp__.*__write.* | MCP 写入工具 |
* | 所有工具 |
区分大小写: Bash ≠ bash
startup|resume|clear|compact)manual|auto){
"hooks": {
"UserPromptSubmit": [
{
"hooks": [{ "type": "command", "command": "/path/to/hook.sh" }]
}
]
}
}
默认类型。执行 bash 命令或脚本。
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
"timeout": 60
}
使用 LLM (Haiku) 进行上下文感知决策。最适合 Stop/SubagentStop。
{
"type": "prompt",
"prompt": "评估 Claude 是否应该停止。上下文:$ARGUMENTS。检查所有任务是否完成。",
"timeout": 30
}
响应模式:
{
"decision": "approve" | "block",
"reason": "解释",
"continue": false,
"stopReason": "给用户的消息",
"systemMessage": "警告"
}
MCP 工具使用模式 mcp__<server>__<tool>:
| 模式 | 匹配项 |
|---|---|
mcp__memory__.* | 所有 memory 服务器工具 |
mcp__.*__write.* | 所有 MCP 写入工具 |
mcp__github__.* | 所有 GitHub 工具 |
| 变量 | 描述 |
|---|---|
CLAUDE_PROJECT_DIR | 项目根目录的绝对路径 |
CLAUDE_CODE_REMOTE | 如果是远程/网页版则为 "true",本地 CLI 则为空 |
| 变量 | 描述 |
|---|---|
CLAUDE_ENV_FILE | 写入 export VAR=value 行的路径 |
| 变量 | 描述 |
|---|---|
CLAUDE_PLUGIN_ROOT | 插件目录的绝对路径 |
| 退出码 | 行为 | stdout | stderr |
|---|---|---|---|
| 0 | 成功 | JSON 已处理 | 忽略 |
| 2 | 阻止错误 | 忽略 | 错误消息 |
| 其他 | 非阻止错误 | 忽略 | 详细模式 |
| 钩子 | 效果 |
|---|---|
| PreToolUse | 阻止工具,stderr 发送给 Claude |
| PostToolUse | stderr 发送给 Claude(工具已运行) |
| UserPromptSubmit | 阻止提示,仅 stderr 发送给用户 |
| Stop | 阻止停止,stderr 发送给 Claude |
#!/bin/bash
set -e
cd "$CLAUDE_PROJECT_DIR/.claude/hooks"
cat | npx tsx src/my-hook.ts
或用于打包版本:
#!/bin/bash
set -e
cd "$HOME/.claude/hooks"
cat | node dist/my-hook.mjs
import { readFileSync } from 'fs';
interface HookInput {
session_id: string;
hook_event_name: string;
tool_name?: string;
tool_input?: Record<string, unknown>;
tool_response?: Record<string, unknown>;
// ... 其他字段,根据钩子类型而定
}
function readStdin(): string {
return readFileSync(0, 'utf-8');
}
async function main() {
const input: HookInput = JSON.parse(readStdin());
// 处理输入
const output = {
decision: 'block', // 或 undefined 表示允许
reason: '阻止原因'
};
console.log(JSON.stringify(output));
}
main().catch(console.error);
# PostToolUse (Write)
echo '{"tool_name":"Write","tool_input":{"file_path":"test.md"},"tool_response":{"success":true},"session_id":"test"}' | \
.claude/hooks/my-hook.sh
# PreToolUse (Bash)
echo '{"tool_name":"Bash","tool_input":{"command":"ls"},"session_id":"test"}' | \
.claude/hooks/my-hook.sh
# SessionStart
echo '{"hook_event_name":"SessionStart","source":"startup","session_id":"test"}' | \
.claude/hooks/session-start.sh
# SessionEnd
echo '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"test"}' | \
.claude/hooks/session-end.sh
# UserPromptSubmit
echo '{"prompt":"test prompt","session_id":"test"}' | \
.claude/hooks/prompt-submit.sh
cd .claude/hooks
npx esbuild src/my-hook.ts \
--bundle --platform=node --format=esm \
--outfile=dist/my-hook.mjs
#!/usr/bin/env python3
import json, sys
data = json.load(sys.stdin)
path = data.get('tool_input', {}).get('file_path', '')
BLOCKED = ['.env', 'secrets.json', '.git/']
if any(b in path for b in BLOCKED):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"Blocked: {path} is protected"
}
}))
else:
print('{}')
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE" == *.ts ]] || [[ "$FILE" == *.tsx ]]; then
npx prettier --write "$FILE" 2>/dev/null
fi
echo '{}'
#!/bin/bash
echo "Git 状态:"
git status --short 2>/dev/null || echo "(不是 git 仓库)"
echo ""
echo "最近提交:"
git log --oneline -5 2>/dev/null || echo "(无提交)"
#!/usr/bin/env python3
import json, sys, subprocess
data = json.load(sys.stdin)
# 防止无限循环
if data.get('stop_hook_active'):
print('{}')
sys.exit(0)
# 检查测试是否通过
result = subprocess.run(['npm', 'test'], capture_output=True)
if result.returncode != 0:
print(json.dumps({
"decision": "block",
"reason": "测试失败。请在停止前修复。"
}))
else:
print('{}')
+x 权限?tool_response 而不是 tool_result?stop_hook_active?$CLAUDE_PROJECT_DIR?tool_response 而不是 tool_resultdecision: "block" + reason 进行阻止echo '{}' | ./hook.sh 测试ls .claude/cache//debug-hooks - 系统化调试工作流.claude/rules/hooks.md - 钩子开发规则每周安装数
196
仓库
GitHub 星标数
3.6K
首次出现
2026年1月22日
安全审计
安装于
opencode189
codex188
gemini-cli185
cursor185
github-copilot183
amp178
Complete reference for developing Claude Code hooks. Use this to write hooks with correct input/output schemas.
| Hook | Fires When | Can Block? | Primary Use |
|---|---|---|---|
| PreToolUse | Before tool executes | YES | Block/modify tool calls |
| PostToolUse | After tool completes | Partial | React to tool results |
| UserPromptSubmit | User sends prompt | YES | Validate/inject context |
| PermissionRequest | Permission dialog shows | YES | Auto-approve/deny |
| SessionStart | Session begins | NO | Load context, set env vars |
| SessionEnd | Session ends | NO | Cleanup/save state |
| Stop | Agent finishes | YES | Force continuation |
| SubagentStart | Subagent spawns | NO | Pattern coordination |
| SubagentStop | Subagent finishes | YES | Force continuation |
| PreCompact | Before compaction | NO | Save state |
| Notification | Notification sent | NO | Custom alerts |
Hook type options: type: "command" (bash) or type: "prompt" (LLM evaluation)
Purpose: Block or modify tool execution before it happens.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "default|plan|acceptEdits|bypassPermissions",
"hook_event_name": "PreToolUse",
"tool_name": "string",
"tool_input": {
"file_path": "string",
"command": "string"
},
"tool_use_id": "string"
}
Output (JSON):
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow|deny|ask",
"permissionDecisionReason": "string",
"updatedInput": {}
},
"continue": true,
"stopReason": "string",
"systemMessage": "string",
"suppressOutput": true
}
Exit code 2: Blocks tool, stderr shown to Claude.
Common matchers: Bash, Edit|Write, Read, Task, mcp__.*
Purpose: React to tool execution results, provide feedback to Claude.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PostToolUse",
"tool_name": "string",
"tool_input": {},
"tool_response": {
"filePath": "string",
"success": true,
"output": "string",
"exitCode": 0
},
"tool_use_id": "string"
}
CRITICAL: The response field is tool_response, NOT tool_result.
Output (JSON):
{
"decision": "block",
"reason": "string",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "string"
},
"continue": true,
"stopReason": "string",
"suppressOutput": true
}
Blocking: "decision": "block" with "reason" prompts Claude to address the issue.
Common matchers: Edit|Write, Bash
Purpose: Validate user prompts, inject context before Claude processes.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "UserPromptSubmit",
"prompt": "string"
}
Output (Plain text):
Any stdout text is added to context for Claude.
Output (JSON):
{
"decision": "block",
"reason": "string",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "string"
}
}
Blocking: "decision": "block" erases prompt, shows "reason" to user only (not Claude).
Exit code 2: Blocks prompt, shows stderr to user only.
Purpose: Automate permission dialog decisions.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PermissionRequest",
"tool_name": "string",
"tool_input": {}
}
Output:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow|deny",
"updatedInput": {},
"message": "string",
"interrupt": false
}
}
}
Purpose: Initialize session, load context, set environment variables.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SessionStart",
"source": "startup|resume|clear|compact"
}
Environment variable: CLAUDE_ENV_FILE - write export VAR=value to persist env vars.
Output (Plain text or JSON):
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "string"
},
"suppressOutput": true
}
Plain text stdout is added as context.
Purpose: Cleanup, save state, log session.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SessionEnd",
"reason": "clear|logout|prompt_input_exit|other"
}
Output: Cannot affect session (already ending). Use for cleanup only.
Purpose: Control when Claude stops, force continuation.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "Stop",
"stop_hook_active": false
}
CRITICAL: Check stop_hook_active: true to prevent infinite loops!
Output:
{
"decision": "block",
"reason": "string"
}
Blocking: "decision": "block" forces Claude to continue with "reason" as prompt.
Purpose: Run when a subagent (Task tool) is spawned.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SubagentStart",
"agent_id": "string"
}
Output: Context injection only (cannot block).
Purpose: Control when subagents (Task tool) stop.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SubagentStop",
"stop_hook_active": false
}
Output: Same as Stop.
Purpose: Save state before context compaction.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PreCompact",
"trigger": "manual|auto",
"custom_instructions": "string"
}
Matchers: manual, auto
Output:
{
"continue": true,
"systemMessage": "string"
}
Purpose: Custom notification handling.
Input:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "Notification",
"message": "string",
"notification_type": "permission_prompt|idle_prompt|auth_success|elicitation_dialog"
}
Matchers: permission_prompt, idle_prompt, auth_success, elicitation_dialog, *
Output:
{
"continue": true,
"suppressOutput": true,
"systemMessage": "string"
}
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
"timeout": 60
}
]
}
]
}
}
| Pattern | Matches |
|---|---|
Bash | Exactly Bash tool |
| `Edit | Write` |
Read.* | Regex: Read* |
mcp__.*__write.* | MCP write tools |
* | All tools |
Case-sensitive: Bash ≠ bash
startup|resume|clear|compact)manual|auto){
"hooks": {
"UserPromptSubmit": [
{
"hooks": [{ "type": "command", "command": "/path/to/hook.sh" }]
}
]
}
}
Default type. Executes bash commands or scripts.
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
"timeout": 60
}
Uses LLM (Haiku) for context-aware decisions. Best for Stop/SubagentStop.
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop. Context: $ARGUMENTS. Check if all tasks are complete.",
"timeout": 30
}
Response schema:
{
"decision": "approve" | "block",
"reason": "Explanation",
"continue": false,
"stopReason": "Message to user",
"systemMessage": "Warning"
}
MCP tools use pattern mcp__<server>__<tool>:
| Pattern | Matches |
|---|---|
mcp__memory__.* | All memory server tools |
mcp__.*__write.* | All MCP write tools |
mcp__github__.* | All GitHub tools |
| Variable | Description |
|---|---|
CLAUDE_PROJECT_DIR | Absolute path to project root |
CLAUDE_CODE_REMOTE | "true" if remote/web, empty if local CLI |
| Variable | Description |
|---|---|
CLAUDE_ENV_FILE | Path to write export VAR=value lines |
| Variable | Description |
|---|---|
CLAUDE_PLUGIN_ROOT | Absolute path to plugin directory |
| Exit Code | Behavior | stdout | stderr |
|---|---|---|---|
| 0 | Success | JSON processed | Ignored |
| 2 | Blocking error | IGNORED | Error message |
| Other | Non-blocking error | Ignored | Verbose mode |
| Hook | Effect |
|---|---|
| PreToolUse | Blocks tool, stderr to Claude |
| PostToolUse | stderr to Claude (tool already ran) |
| UserPromptSubmit | Blocks prompt, stderr to user only |
| Stop | Blocks stop, stderr to Claude |
#!/bin/bash
set -e
cd "$CLAUDE_PROJECT_DIR/.claude/hooks"
cat | npx tsx src/my-hook.ts
Or for bundled:
#!/bin/bash
set -e
cd "$HOME/.claude/hooks"
cat | node dist/my-hook.mjs
import { readFileSync } from 'fs';
interface HookInput {
session_id: string;
hook_event_name: string;
tool_name?: string;
tool_input?: Record<string, unknown>;
tool_response?: Record<string, unknown>;
// ... other fields per hook type
}
function readStdin(): string {
return readFileSync(0, 'utf-8');
}
async function main() {
const input: HookInput = JSON.parse(readStdin());
// Process input
const output = {
decision: 'block', // or undefined to allow
reason: 'Why blocking'
};
console.log(JSON.stringify(output));
}
main().catch(console.error);
# PostToolUse (Write)
echo '{"tool_name":"Write","tool_input":{"file_path":"test.md"},"tool_response":{"success":true},"session_id":"test"}' | \
.claude/hooks/my-hook.sh
# PreToolUse (Bash)
echo '{"tool_name":"Bash","tool_input":{"command":"ls"},"session_id":"test"}' | \
.claude/hooks/my-hook.sh
# SessionStart
echo '{"hook_event_name":"SessionStart","source":"startup","session_id":"test"}' | \
.claude/hooks/session-start.sh
# SessionEnd
echo '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"test"}' | \
.claude/hooks/session-end.sh
# UserPromptSubmit
echo '{"prompt":"test prompt","session_id":"test"}' | \
.claude/hooks/prompt-submit.sh
cd .claude/hooks
npx esbuild src/my-hook.ts \
--bundle --platform=node --format=esm \
--outfile=dist/my-hook.mjs
#!/usr/bin/env python3
import json, sys
data = json.load(sys.stdin)
path = data.get('tool_input', {}).get('file_path', '')
BLOCKED = ['.env', 'secrets.json', '.git/']
if any(b in path for b in BLOCKED):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"Blocked: {path} is protected"
}
}))
else:
print('{}')
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE" == *.ts ]] || [[ "$FILE" == *.tsx ]]; then
npx prettier --write "$FILE" 2>/dev/null
fi
echo '{}'
#!/bin/bash
echo "Git status:"
git status --short 2>/dev/null || echo "(not a git repo)"
echo ""
echo "Recent commits:"
git log --oneline -5 2>/dev/null || echo "(no commits)"
#!/usr/bin/env python3
import json, sys, subprocess
data = json.load(sys.stdin)
# Prevent infinite loops
if data.get('stop_hook_active'):
print('{}')
sys.exit(0)
# Check if tests pass
result = subprocess.run(['npm', 'test'], capture_output=True)
if result.returncode != 0:
print(json.dumps({
"decision": "block",
"reason": "Tests are failing. Please fix before stopping."
}))
else:
print('{}')
+x permission?tool_response not tool_result?stop_hook_active in Stop hooks?$CLAUDE_PROJECT_DIR for paths?tool_response not tool_resultdecision: "block" + reason for blockingecho '{}' | ./hook.sh before relying on itls .claude/cache/ before editing code/debug-hooks - Systematic debugging workflow.claude/rules/hooks.md - Hook development rulesWeekly Installs
196
Repository
GitHub Stars
3.6K
First Seen
Jan 22, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode189
codex188
gemini-cli185
cursor185
github-copilot183
amp178
agent-browser 浏览器自动化工具 - Vercel Labs 命令行网页操作与测试
150,000 周安装