解决方案设计
解决方案 最佳实践
设计 Hook 使用场景的最佳实践方案
Claude Code Hook 最佳实践
原生 Hook 使用方案
Claude Code 的 Hook 设计强调开箱即用和低门槛配置。以下是几种典型场景的最佳实践:
场景一:自动代码格式化
最常见的 Hook 用例是在文件编辑后自动运行格式化工具。这消除了手动运行 Prettier 的繁琐:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
场景二:危险命令拦截
在 PreToolUse 中拦截危险的 Bash 命令,防止误操作:
#!/bin/bash
# .claude/hooks/block-dangerous.sh
COMMAND=$(jq -r '.tool_input.command')
# 危险命令模式
DANGEROUS_PATTERNS=("rm -rf" "rm -rf /" "DROP TABLE" "truncate" ":(){ :|:& };:")
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if [[ "$COMMAND" == *"$pattern"* ]]; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "危险命令已拦截: 包含 '"$pattern"'"
}
}'
exit 0
fi
done
exit 0 # 允许执行
场景三:Ralph-Loop 自主循环
利用 Stop Hook 实现 Claude 的自主循环,在任务未完成时自动继续:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/stop-hook.sh"
}
]
}
]
}
}
#!/bin/bash
# stop-hook.sh
INPUT=$(cat)
# 检查是否已经触发过
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # 允许停止
fi
# 检查 TODO 是否全部完成
TODO_STATUS=$(cat .opencode/.boulder-state 2>/dev/null | jq -r '.all_completed')
if [ "$TODO_STATUS" != "true" ]; then
# 返回继续指令
jq -n '{
decision: "block",
reason: "任务尚未完成,请继续工作。检查未完成的 TODO 项目。"
}'
fi
配置示例
完整的多功能 Hook 配置:
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo '提醒: 使用 Bun 而非 npm。运行 bun test 后再提交。'"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-bash.sh"
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs -I {} npx prettier --write {}"
}
]
}
],
"Notification": [
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude 需要您的输入\" with title \"Claude Code\"'"
}
]
}
]
}
}
OpenCode Hook 最佳实践
原生 Hook 使用方案
OpenCode 的 Hook 作为插件 API 的一部分,更适合构建复杂功能而非简单自动化:
场景一:敏感文件保护
// .opencode/plugins/env-protection.ts
import type { Plugin } from "@opencode-ai/plugin"
export const EnvProtection: Plugin = async () => {
return {
"tool.execute.before": async (input, output) => {
// 阻止读取 .env 文件
if (input.tool === "read" && output.args.filePath.includes(".env")) {
throw new Error("禁止读取 .env 文件,这是安全策略")
}
// 阻止写入敏感路径
if (input.tool === "write") {
const protectedPaths = [".env", "credentials.json", "secrets.yaml"]
for (const protected of protectedPaths) {
if (output.args.filePath.includes(protected)) {
throw new Error(`禁止写入敏感文件: ${protected}`)
}
}
}
}
}
}
场景二:环境变量注入
// .opencode/plugins/inject-env.ts
import type { Plugin } from "@opencode-ai/plugin"
export const InjectEnv: Plugin = async () => {
return {
"shell.env": async (input, output) => {
// 自动注入项目相关环境变量
output.env.PROJECT_ROOT = input.cwd
output.env.NODE_ENV = process.env.NODE_ENV || "development"
// 从 .env 文件加载(如果有)
const envPath = `${input.cwd}/.env`
try {
const envContent = await Bun.file(envPath).text()
envContent.split("\n").forEach(line => {
const [key, ...values] = line.split("=")
if (key && values.length) {
output.env[key.trim()] = values.join("=").trim()
}
})
} catch {}
}
}
}
场景三:自定义工具注册
// .opencode/plugins/custom-tools.ts
import { type Plugin, tool } from "@opencode-ai/plugin"
export const CustomTools: Plugin = async (ctx) => {
return {
tool: {
// 注册 Jira 集成工具
jira_search: tool({
description: "搜索 Jira 工单",
args: {
query: tool.schema.string().describe("搜索关键词")
},
async execute(args, context) {
const response = await fetch(
`https://your-company.atlassian.net/rest/api/3/search?jql=text~"${args.query}"`,
{
headers: {
Authorization: `Bearer ${process.env.JIRA_TOKEN}`
}
}
)
const data = await response.json()
return JSON.stringify(data.issues, null, 2)
}
}),
// 注册数据库查询工具
db_query: tool({
description: "执行只读数据库查询",
args: {
sql: tool.schema.string().describe("SELECT 查询语句")
},
async execute(args, context) {
if (!args.sql.toUpperCase().startsWith("SELECT")) {
throw new Error("只允许 SELECT 查询")
}
// 实现数据库查询逻辑
return "查询结果..."
}
})
}
}
}
配置示例
oh-my-opencode 风格的复杂插件结构:
// .opencode/plugins/my-plugin/index.ts
import type { Plugin } from "@opencode-ai/plugin"
import { createTodoEnforcerHook } from "./hooks/todo-enforcer"
import { createContextInjectorHook } from "./hooks/context-injector"
import { createNotificationHook } from "./hooks/notification"
export const MyPlugin: Plugin = async (ctx) => {
const { project, client, $, directory, worktree } = ctx
// 初始化日志
await client.app.log({
body: {
service: "my-plugin",
level: "info",
message: "插件初始化中..."
}
})
return {
// 会话空闲时发送通知
"session.idle": createNotificationHook(ctx),
// 工具执行前检查
"tool.execute.before": async (input, output) => {
// 实现 TODO 强制完成检查
// 实现上下文窗口监控
// 实现敏感操作拦截
},
// 会话压缩时注入上下文
"experimental.session.compacting": async (input, output) => {
output.context.push(`
## 持久化上下文
- 当前任务: ${project.name}
- 工作目录: ${directory}
- 重要决策: [记录关键决策]
`)
},
// 注册自定义工具
tool: {
my_custom_tool: tool({
description: "自定义工具描述",
args: {
param: tool.schema.string()
},
async execute(args, context) {
return "工具执行结果"
}
})
}
}
}
架构对比图
数据流设计
graph TB
subgraph "Claude Code Hook 数据流"
CC1[Claude Code 主进程] -->|事件触发| CC2[Hook 匹配器]
CC2 -->|匹配成功| CC3[启动子进程]
CC3 -->|stdin: JSON 输入| CC4[Hook 脚本]
CC4 -->|stdout: JSON 输出| CC3
CC3 -->|Exit Code| CC1
CC1 -->|处理结果| CC5[继续/阻断/询问]
end
subgraph "OpenCode Hook 数据流"
OC1[OpenCode Runtime] -->|事件触发| OC2[Plugin Hook 函数]
OC2 -->|直接调用| OC3[Hook Handler]
OC3 -->|修改 output 对象| OC2
OC3 -->|抛出异常| OC2
OC2 -->|返回结果| OC1
OC1 -->|继续执行/阻断| OC4[下一步操作]
end
执行流程对比
| 阶段 | Claude Code | OpenCode |
|---|---|---|
| 配置加载 | 启动时读取 JSON,支持热重载 | 插件加载时执行函数 |
| 事件触发 | 按事件类型查找匹配的 Hook | 直接调用已注册的 Hook 函数 |
| 参数传递 | JSON 序列化到 stdin | 直接传递 JavaScript 对象 |
| 结果返回 | 解析 stdout JSON / Exit Code | 函数返回值 / 对象修改 |
| 错误处理 | 非 0 Exit Code = 错误 | 异常 = 错误 |
| 超时控制 | 内置 timeout 字段 | 需自行实现 |
性能对比:
| 指标 | Claude Code | OpenCode |
|---|---|---|
| Hook 调用延迟 | ~50-200ms(进程启动) | ~1-5ms(函数调用) |
| 内存隔离 | 完全隔离 | 共享运行时 |
| 崩溃影响 | 不影响主进程 | 可能导致主进程崩溃 |
| 跨语言支持 | 任意语言脚本 | JavaScript/TypeScript |
参考资料
- Claude Code Hooks Guide - 官方入门指南
- Ralph-Loop 实现分析 - Stop Hook 高级用法
- oh-my-opencode Hook 实现 - 33 个 Hook 源码分析
- OpenCode Plugin SDK - 插件开发 SDK