跳转到内容

Hook 系统与会话生命周期管理

Hook 系统与会话生命周期管理

学习目标

  • 理解 SessionStart 钩子链在 agent-skills 中的实现
  • 掌握 SDD 缓存管理的前置/后置钩子模式
  • 理解 Code-Simplify 忽略规则引擎的设计
  • 学会应用 hook 的 fail-open 原则

前置知识

本章涉及 Hook 事件驱动安全机制的通用原理,建议先阅读:

下文直接聚焦 agent-skills 的具体 Hook 实现。

项目实践

SessionStart 链式执行

{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"
}
]
}
]
}
}

hooks.json 定义了单一的 SessionStart 钩子链。使用 ${CLAUDE_PLUGIN_ROOT} 变量确保钩子脚本路径在插件安装时自动解析。

session-start.sh 的功能

  • 检测项目类型和技术栈
  • 注入项目级上下文
  • 加载可用技能列表

SDD 缓存管理(前置 + 后置钩子)

agent-skills 实现了成对钩子模式来管理 Spec-Driven Development 的缓存:

钩子触发时机功能
sdd-cache-pre.sh会话启动时检查 SDD 缓存是否存在、是否过期
sdd-cache-post.sh规格编写完成后将规格写入缓存,供后续会话复用

缓存生命周期

Session Start
sdd-cache-pre.sh 检查缓存
├── 命中 → 注入缓存内容到上下文
└── 未命中 → 空状态,需要重新编写规格
规格编写完成
sdd-cache-post.sh 写入缓存

缓存键设计:基于项目路径和规格内容的 hash。这使得同一项目的不同规格版本不会互相覆盖,同时允许跨会话复用规格。

Code-Simplify 忽略规则引擎

simplify-ignore.sh(12KB+)是一个复杂的忽略规则引擎,决定哪些文件/变更不应该被 code-simplify 处理:

Terminal window
# 伪代码:simplify-ignore.sh 的核心逻辑
set -e
# 1. 检查文件路径模式
if echo "$file" | grep -qE "^(node_modules|vendor|\.git|dist)/"; then
echo '{"ignore": true, "reason": "generated/vendor directory"}'
exit 0
fi
# 2. 检查变更类型
if [ "$change_type" = "added" ] && [ "$file_size" -lt 100 ]; then
echo '{"ignore": true, "reason": "small new file, no simplification needed"}'
exit 0
fi
# 3. 检查文件大小
if [ "$file_size" -gt 50000 ]; then
echo '{"ignore": true, "reason": "file too large for simplification"}'
exit 0
fi
# 4. 检查项目级自定义规则
if [ -f ".simplify-ignore" ]; then
# 读取项目级规则,追加到默认规则
...
fi
# 默认:不忽略
echo '{"ignore": false}'

配套的 simplify-ignore-test.sh 验证规则的正确性,确保新规则不意外忽略应处理的文件。

Fail-Open 原则

agent-skills 的所有钩子遵循 fail-open 原则:钩子失败不应阻断会话。具体实现:

#!/bin/bash
set +e # 不因为错误退出
# 钩子逻辑
result=$(do_something) || {
echo "Hook failed, proceeding with default behavior" >&2
echo '{"status": "ok", "hook_failed": true}'
exit 0 # 即使失败也返回成功状态
}

为什么 fail-open:钩子是优化,不是安全门控。如果钩子失败阻断会话,用户体验会极差。fail-open 保证生产力不被阻断。

问题与规避

陷阱表现对策
钩子脚本超时会话启动延迟过长设置合理的超时限制(通常 30 秒)
缓存过期但不刷新Agent 使用过时规格基于内容 hash 的缓存键,规格变更自动失效
忽略规则过宽应该简化的文件被跳过定期运行测试脚本验证规则
忽略规则过窄不必要的文件被处理基于文件路径模式精确匹配

设计取舍

为什么用成对钩子(pre/post)而非单一状态管理?

成对钩子的优势是无状态——不需要在会话之间维护状态文件。pre 钩子检查缓存状态,post 钩子写入缓存状态。如果 post 钩子失败,下一次 pre 钩子会检测到缓存缺失并触发重新生成。这与需要维护状态文件的方案相比,更容错。

为什么 simplify-ignore.sh 是 Bash 而非 Python?

Bash 脚本可以直接在 hook 命令中执行,不需要额外的依赖安装。虽然 Python 更强大,但要求每个用户安装 Python 环境和依赖会增加安装门槛。12KB 的 Bash 脚本虽然不如 Python 优雅,但确保了最大兼容性。

参考来源

  • 源码验证: hooks/hooks.json — 钩子配置
  • 源码验证: hooks/session-start.sh — 会话启动钩子
  • 源码验证: hooks/sdd-cache-pre.sh / hooks/sdd-cache-post.sh — SDD 缓存管理
  • 源码验证: hooks/simplify-ignore.sh — Code-Simplify 忽略规则引擎
  • 源码验证: hooks/SDD-CACHE.md / hooks/SIMPLIFY-IGNORE.md — 钩子行为文档