- Introduced a detailed report outlining compliance issues and recommendations for the `ccw/frontend` implementation of Hook templates. - Identified critical issues regarding command structure and input reading methods. - Highlighted errors related to cross-platform compatibility of Bash scripts on Windows. - Documented warnings regarding matcher formats and exit code usage. - Provided a summary of supported trigger types and outlined missing triggers. - Included a section on completed fixes and references to affected files for easier tracking.
8.0 KiB
Hook 模板分析报告
基于 Claude Code 官方 Hook 规范对
ccw/frontend前端实现的检查
概要
| 检查项 | 状态 | 严重级别 |
|---|---|---|
| 触发器类型支持 | 12/18 支持 | ⚠️ 缺失 6 种 |
| 命令结构格式 | 不合规 | 🔴 CRITICAL |
| 输入读取方式 | 不合规 | 🔴 CRITICAL |
| Bash 脚本跨平台 | 不兼容 | 🟠 ERROR |
| JSON 决策输出 | 合规 | ✅ 正确 |
| Matcher 格式 | 部分问题 | ⚠️ WARNING |
1. CRITICAL 问题
1.1 命令结构:command + args 数组格式
官方规范:使用单一 command 字符串
{
"type": "command",
"command": "bash .claude/hooks/validate.sh",
"timeout": 30
}
当前实现:使用 command + args 数组
command: 'node',
args: ['-e', 'const cp=require("child_process");...']
影响文件:
HookQuickTemplates.tsx第 77-229 行(所有 16 个模板)HookWizard.tsx第 87-148 行(HOOK_TEMPLATES 对象)
修复方案:
// 错误格式
command: 'node',
args: ['-e', 'script...']
// 正确格式
command: "node -e 'script...'"
1.2 输入读取:process.env.HOOK_INPUT vs stdin
官方规范:Hook 输入通过 stdin 传入 JSON
输入:JSON 通过 stdin 传入
当前实现:使用环境变量
const p=JSON.parse(process.env.HOOK_INPUT||"{}");
影响位置:
HookQuickTemplates.tsx: 第 93, 120, 133, 146, 172, 184, 228 行
修复方案:
// Node.js 内联脚本
const fs=require('fs');const p=JSON.parse(fs.readFileSync(0,'utf8')||"{}");
// Bash 脚本
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command')
2. ERROR 问题
2.1 Bash 脚本在 Windows 上失败
问题:HookWizard.tsx 中所有 danger-* 模板使用 bash -c:
command: 'bash',
args: ['-c', 'INPUT=$(cat); CMD=$(echo "$INPUT" | jq -r ...']
失败原因:
- Windows 默认没有
bash(需要 WSL 或 Git Bash) - 使用 Unix 命令:
cat,jq,grep -qiE - 使用 Unix shell 语法:
$(...),if; then; fi
影响模板:
danger-bash-confirm(第 106-112 行)danger-file-protection(第 113-119 行)danger-git-destructive(第 120-126 行)danger-network-confirm(第 127-133 行)danger-system-paths(第 134-140 行)danger-permission-change(第 141-147 行)
修复方案:
- 使用
node -e替代bash -c(跨平台) - 或提供 PowerShell 版本的检测脚本
- 或在运行时检测平台并选择对应脚本
2.2 平台检测使用浏览器 UA
问题:convertToClaudeCodeFormat 函数(第 185 行)
const isWindows = typeof navigator !== 'undefined' && navigator.userAgent.includes('Win');
错误场景:用户在 Mac 浏览器中配置,但 Hook 在远程 Windows 机器执行
修复方案:从后端 API 获取实际执行平台信息
3. WARNING 问题
3.1 无效 MCP Matcher 格式
位置:HookQuickTemplates.tsx 第 224 行
matcher: 'core_memory'
官方规范:MCP 工具命名格式 mcp__<server>__<tool>
// 正确格式
matcher: 'mcp__ccw-tools__core_memory'
3.2 空 Matcher 滥用
位置:HookWizard.tsx 第 90, 96, 102 行
空 matcher '' 是有效的,但意味着 Hook 会对该事件类型的所有工具触发。对于 Stop 事件没有问题,但需要确认是否为预期行为。
3.3 引号转义脆弱
位置:HookWizard.tsx 第 173, 187-191 行
当前转义逻辑:
// bash 脚本
const escapedScript = script.replace(/'/g, "'\\''");
// Windows node 脚本
const escapedScript = script.replace(/"/g, '\\"');
问题:对于包含反引号、$()、嵌套引号的复杂脚本可能失败
3.4 Exit Code 2 未使用
官方规范:exit code 2 用于阻止操作并显示反馈
当前状态:仅 block-sensitive-files 模板使用 process.exit(2),其他 danger-* 模板通过 JSON 输出 permissionDecision 但未配合 exit code
修复:在输出 deny 决策后应使用 exit 2
echo '{"hookSpecificOutput":{...}}' && exit 0 # 当前
echo '{"hookSpecificOutput":{...}}' && exit 2 # 应改为 exit 2 以阻止
4. 触发器类型支持情况
4.1 完整支持表
| 触发器 | 代码支持 | UI 过滤器 | 状态 |
|---|---|---|---|
| SessionStart | ✅ | ✅ | 完整 |
| SessionEnd | ✅ | ❌ | 代码有,UI 无 |
| UserPromptSubmit | ✅ | ✅ | 完整 |
| PreToolUse | ✅ | ✅ | 完整 |
| PostToolUse | ✅ | ✅ | 完整 |
| PostToolUseFailure | ✅ | ❌ | 代码有,UI 无 |
| PermissionRequest | ✅ | ❌ | 代码有,UI 无 |
| Notification | ✅ | ❌ | 代码有,UI 无 |
| Stop | ✅ | ✅ | 完整 |
| SubagentStart | ✅ | ❌ | 代码有,UI 无 |
| SubagentStop | ✅ | ❌ | 代码有,UI 无 |
| PreCompact | ✅ | ❌ | 代码有,UI 无 |
| TeammateIdle | ❌ | ❌ | 缺失 |
| TaskCompleted | ❌ | ❌ | 缺失 |
| ConfigChange | ❌ | ❌ | 缺失 |
| WorktreeCreate | ❌ | ❌ | 缺失 |
| WorktreeRemove | ❌ | ❌ | 缺失 |
4.2 需要添加的触发器
缺失的 6 种触发器:
TeammateIdle- 团队成员空闲时触发TaskCompleted- 任务标记完成时触发ConfigChange- 配置文件外部修改时触发WorktreeCreate- 工作树创建时触发WorktreeRemove- 工作树移除时触发
5. 正确实现的部分
| 项目 | 状态 | 说明 |
|---|---|---|
| 触发器类型名称 | ✅ | 使用的触发器名称符合官方规范 |
| Matcher 正则语法 | ✅ | Write|Edit、Bash 等格式正确 |
| Timeout 单位转换 | ✅ | 毫秒→秒转换正确 |
| JSON 决策输出格式 | ✅ | hookSpecificOutput 结构符合规范 |
| Bash stdin 读取 | ✅ | INPUT=$(cat) 方式正确 |
6. 修复优先级
P0 - 必须立即修复 ✅ 已修复
- 命令格式:将
command+args合并为单一字符串 - 输入读取:将
process.env.HOOK_INPUT改为 stdin 读取
P1 - 尽快修复 ✅ 已修复
- Windows 兼容:将
bash -c脚本改为node -e或提供 PowerShell 版本 - Exit code 2:在 deny 场景使用正确的 exit code
P2 - 后续优化
- 缺失触发器:添加 6 种缺失的触发器类型支持
- UI 过滤器:将所有支持的触发器添加到过滤器
- MCP Matcher:修正
core_memory为mcp__ccw-tools__core_memory✅ 已修复 - 平台检测:从后端获取实际执行平台
7. 已完成的修复
7.1 后端修复
文件: ccw/src/core/routes/system-routes.ts
installRecommendedHook函数 (第 216-231 行)- 修复:使用官方嵌套格式
{ matcher: '', hooks: [{ type: 'command', command: '...', timeout: 5 }] } - 修复:timeout 从毫秒改为秒
文件: ccw/src/core/routes/hooks-routes.ts
- 新增
normalizeHookFormat函数 - 自动将旧格式转换为新格式
- 自动将 timeout 从毫秒转换为秒
7.2 前端修复
文件: ccw/frontend/src/components/hook/HookQuickTemplates.tsx
- 所有模板从
process.env.HOOK_INPUT改为fs.readFileSync(0, 'utf8') - 修复
memory-sync-dashboard的 matcher:core_memory→mcp__ccw-tools__core_memory
文件: ccw/frontend/src/components/hook/HookWizard.tsx
skill-context-keyword和skill-context-auto模板修复 stdin 读取danger-file-protection和danger-system-paths的 deny 决策使用 exit 2
7. 文件位置参考
| 文件 | 主要问题 |
|---|---|
src/components/hook/HookQuickTemplates.tsx |
命令格式、输入读取、MCP matcher |
src/components/hook/HookWizard.tsx |
Bash 跨平台、命令格式、平台检测 |
src/pages/HookManagerPage.tsx |
缺失触发器类型、UI 过滤器不完整 |
src/components/hook/HookCard.tsx |
HookTriggerType 类型定义不完整 |