VS Code Copilot Hooks(.json):Agent 会话的确定性生命周期自动化机制详解
本文基于 VS Code 仓库中 Copilot 定制技能包内的 Hooks 参考文档(hooks.md)展开,系统讲解 GitHub Copilot Agent Hooks 的配置文件位置、事件类型、JSON 配置格式与输入输出契约,并结合 VS Code 源码中的解析器、JSON Schema 与发现机制,说明每条配置字段在运行时如何被加载、解析和执行,帮助你在团队级 Agent 策略(如拦截危险命令、强制校验、自动注入上下文)落地时做到"配置即可用、原理可追溯"。
1. Hooks 是什么:确定性执行 vs 指导性提示
Agent 的定制能力可以分为两大类。Copilot 定制技能(SKILL.md)给出了完整的决策矩阵:
| 定制原语 | 行为特征 |
|---|---|
| Instructions / Prompts / Skills / Agents | Guidance(指导性,非确定性) |
| Hooks | Runtime enforcement(运行时强制)与确定性自动化 |
原文档的定位是:Hooks 是对 agent 会话的确定性生命周期自动化(deterministic lifecycle automation)——用于强制策略、自动化校验、注入运行时上下文。当行为必须被"保证"发生,而不是"希望"发生时,才应使用 hooks,例如:阻止危险命令、强制代码校验、自动注入上下文。
这一点与 instructions 的区别非常关键:instructions 依赖模型"听从"提示,是概率性的;hooks 则在 PreToolUse、PostToolUse 等生命周期节点上真实地执行 shell 命令,命令的输出与退出码直接决定流程继续还是阻断,是确定性的。
2. Hook 配置文件的存放位置
原文档给出了四个标准位置:
| 路径 | 作用域 |
|---|---|
.github/hooks/*.json |
工作区(团队共享,随仓库提交) |
.claude/settings.local.json |
工作区本地(不提交) |
.claude/settings.json |
工作区 |
~/.claude/settings.json |
用户主目录 |
所有已配置位置中的 hooks 都会被收集并执行,工作区 hooks 与用户 hooks 互不覆盖。
从源码结构看,这一"收集而非覆盖"语义有明确实现:
- 发现逻辑位于 sessionCustomizationDiscovery.ts,它声明了 hook 类文件的扫描位置,除文档所列路径外,还包含
.github/copilot/settings.json、.github/copilot/settings.local.json以及.claude/settings.json、.claude/settings.local.json; - 合并逻辑在 hookSchema.ts 的 mergeHooks 中:对每一种 hook 类型,附加的 hooks 数组被追加到基础数组之后(
result[hookType] = baseArr ? [...baseArr, ...additionalArr] : additionalArr),即同事件下多个来源的命令按顺序全部执行,而非后者覆盖前者; - 测试 sessionCustomizationDiscovery.test.ts 验证了多来源并存的行为,例如工作区
.github/hooks/pre-tool.json与用户目录~/.copilot/hooks/post-tool.json同时被发现,且非.json文件(如not-a-hook.md)会被忽略,.github/copilot/settings.local.json与.claude/settings.local.json中的hooks字段同样被识别为 hook 来源。
多根工作区的一个细节:从 sessionState.ts 中的注释可以推断,Copilot agent 只会应用"主工作目录"下的 hooks,因此当只有一个工作区文件夹在 .github/hooks/ 下带有 hooks 时,系统会自动固定该文件夹为主目录并隐藏目录选择器;当多个文件夹都带有 hooks 时,会弹出选择器让用户消歧。
此外,插件格式的 hooks 也有各自的位置约定,见 pluginParsers.ts:Copilot 插件使用根目录的 hooks.json,Claude 插件与 OpenPlugin 格式使用 hooks/hooks.json。
3. Hook 事件类型
原文档列出的 8 个事件及其触发时机:
| 事件 | 触发时机 |
|---|---|
SessionStart |
新 agent 会话的第一个 prompt |
UserPromptSubmit |
用户提交 prompt |
PreToolUse |
工具调用之前 |
PostToolUse |
工具调用成功之后 |
PreCompact |
上下文压缩之前 |
SubagentStart |
子代理(subagent)启动 |
SubagentStop |
子代理结束 |
Stop |
Agent 会话结束 |
源码中 hookTypes.ts 的 HookType 枚举定义了完整的 10 个事件:除了上述 8 个,还有 SessionEnd 与 ErrorOccurred。HOOKS_BY_TARGET 表(hookTypes.ts L30-L68)按目标区分了每个后端支持的事件集合:
- VS Code 格式:恰好支持文档所列的 8 个 PascalCase 事件(
SessionStart...Stop); - Claude 格式:与 VS Code 相同(无
SessionEnd/ErrorOccurred); - GitHub Copilot CLI 格式:使用 camelCase 命名(
sessionStart、userPromptSubmitted、preToolUse等),且独有sessionEnd与errorOccurred; - 未指定 target 时:列出全部已知事件。
同时,HOOK_METADATA(同文件 L81-L122)为每个事件提供了本地化描述,例如 PreToolUse 为"Executed before the agent uses any tool",可直接对照原文档的 Trigger 列。
4. 配置格式
4.1 最小完整示例
原文档给出的标准配置:
{
"hooks": {
"PreToolUse": [
{
"type": "command",
"command": "./scripts/validate-tool.sh",
"timeout": 15
}
]
}
}
每个事件对应一个 hook 命令数组。源码中的 JSON Schema(hookSchema.ts 的 vscodeHookCommandSchema)进一步明确了约束:
type是必填字段,且只能取值"command";command、windows、linux、osx中至少提供一个(错误提示:"At least one of 'command', 'windows', 'linux', or 'osx' must be specified");- 文件顶层
hooks字段必填。
4.2 每个 hook 命令支持的字段
原文档列出:type(必须为 command)、command(默认)、windows/linux/osx(平台覆盖)、cwd、env、timeout。结合 HOOK_COMMAND_FIELD_DESCRIPTIONS 可补全每个字段的语义:
| 字段 | 说明 |
|---|---|
type |
必须为 "command" |
command |
要执行的默认跨平台命令 |
windows / linux / osx |
对应平台特定命令;在该平台运行时覆盖 command 字段 |
bash |
Linux 与 macOS 的 Bash 命令(兼容格式,解析时映射到 linux + osx) |
powershell |
Windows 的 PowerShell 命令(解析时映射到 windows) |
cwd |
脚本工作目录,相对路径相对于仓库根目录,支持 ~ 展开 |
env |
附加环境变量,与既有环境合并(值为字符串的对象) |
timeout |
最长执行时间(秒),默认 30 |
timeoutSec |
Copilot CLI 格式下使用,默认 10 |
平台覆盖的实际解析逻辑在 resolveEffectiveCommand 中:按当前操作系统选择 windows/osx/linux 字段,未命中则回退到 command,与调试配置 launch.json 的按平台配置处理思路一致。
cwd 的路径解析在 resolveHookCommand 中:先做 ~ 展开,绝对路径直接使用,相对路径则与 workspace root 拼接;未指定 cwd 时默认就是 workspace root。
格式自动识别:hookFileSchema(hookSchema.ts L237-L301)通过文件中是否存在数值型 version 字段来区分两种格式——带 version 的文件按 Copilot CLI 格式解析(camelCase 事件名 + bash/powershell/timeoutSec 字段),否则按 VS Code PascalCase 格式解析。编辑器内打开 hook 文件时还可使用内置的 Basic hook configuration 片段。
4.3 兼容 Claude 风格的嵌套写法
extractHookCommandsFromItem 表明,当 Claude 风格的条目被粘贴进来时也能被正确解析:
- 直接命令对象:
{ "type": "command", "command": "..." }; - 带
matcher的嵌套结构:{ "matcher": "...", "hooks": [{ "type": "command", ... }] }; - Claude 格式允许省略
type字段,解析时会按command处理(normalizeForResolve)。
Claude 风格的 bash 字段会被同时映射到 linux 与 osx,powershell 映射到 windows(normalizeHookCommand)。
5. 输入 / 输出契约
Hooks 通过 stdin 接收 JSON,可以经 stdout 返回 JSON。
- 通用输出字段:
continue、stopReason、systemMessage; PreToolUse的权限决策从hookSpecificOutput.permissionDecision读取,取值为allow|ask|deny;PostToolUse的输出可用decision: block阻止后续处理。
原文档给出的 PreToolUse 示例输出:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "ask",
"permissionDecisionReason": "Needs user confirmation"
}
}
源码中 Claude 会话的 SDK 选项 claudeSdkOptions.ts 同样构造了 hookSpecificOutput 结构,可印证该契约是跨后端的通用约定。
退出码约定:
| 退出码 | 语义 |
|---|---|
0 |
成功 |
2 |
阻断性错误(blocking error) |
| 其他值 | 产生非阻断性警告(non-blocking warnings) |
也就是说,hook 脚本不必总是以 JSON 表达决策:直接 exit 2 即可阻断流程,exit 1 等其他非零值会给出警告但不中断。
6. 源码级执行链路:一份 hook 配置如何生效
将上述模块串起来,一条 PreToolUse hook 从磁盘文件到真正执行的链路(基于源码结构梳理)大致是:
- 发现:agent 会话启动时,会话定制发现逻辑扫描
.github/hooks/*.json、.github/copilot/settings*.json、.claude/settings*.json等位置(sessionCustomizationDiscovery.ts),非 JSON 文件被忽略; - 解析:各格式解析器(pluginParsers.ts)调用
parseHooksJson,逐条经normalizeHookCommand→resolveHookCommand得到规范化的IHookCommand对象; - 合并:不同来源的 hooks 按事件类型用
mergeHooks追加合并,形成ChatRequestHooks(按事件分组的命令表),传递给扩展宿主; - 触发:agent 运行到对应生命周期节点时执行命令,平台覆盖(
windows/linux/osx)按当前 OS 解析出实际执行的命令,退出码与 stdout JSON 决定继续、警告或阻断。
子代理内联 hooks:除了独立 .json 文件,hooks 还可以内联在自定义 agent 的 YAML frontmatter 中。parseSubagentHooksFromYaml 解析 hooks 属性,支持两种写法(直接命令列表,或 Claude 风格带 matcher 的嵌套结构),并按 agent 的 target 正确解析事件名。SKILL.md 也明确:"Hooks can be defined in standalone .json files or inline in custom agent frontmatter via the hooks attribute"。
7. 最佳实践与反模式
原文档给出的四条核心原则与三条反模式,是团队落地 hooks 时最值得遵守的约束:
核心原则
- 保持 hook 小而可审计(Keep hooks small and auditable)——脚本短小、逻辑单点,便于 code review;
- 校验并清洗 hook 输入(Validate and sanitize hook inputs)——stdin 收到的 JSON 应做结构校验,不要盲目信任;
- 脚本中不要硬编码密钥(Avoid hardcoded secrets in scripts);
- 团队策略用工作区 hooks,个人自动化用用户 hooks(Prefer workspace hooks for team policy, user hooks for personal automation)——这与第 2 节"多来源全部执行、互不覆盖"的机制配合,形成清晰的职责分层。
反模式
- 运行长时间阻塞正常流程的 hook(会拖慢每次工具调用,
timeout默认 30 秒也意味着长任务会频繁触发超时); - 在普通 instructions 就足够的场景滥用 hooks(hooks 是强制手段,提示能满足的需求不要升级);
- 允许 agent 在无审批控制的情况下编辑 hook 脚本——hook 脚本本身具备拦截工具调用的权力,若 agent 可自由修改它们,策略防线形同虚设,应把 hook 脚本纳入人工审批或 CI 校验。
8. 相关文件索引
| 内容 | 路径 |
|---|---|
| 本文档(原始参考) | extensions/copilot/assets/prompts/skills/agent-customization/references/hooks.md |
| Agent 定制总览与决策矩阵 | extensions/copilot/assets/prompts/skills/agent-customization/SKILL.md |
| 事件枚举、按目标的格式映射与本地化元数据 | src/vs/workbench/contrib/chat/common/promptSyntax/hookTypes.ts |
| JSON Schema、字段语义、平台覆盖与路径解析 | src/vs/workbench/contrib/chat/common/promptSyntax/hookSchema.ts |
| 会话级 hook 位置发现 | src/vs/platform/agentHost/node/copilot/sessionCustomizationDiscovery.ts |
| 插件格式与 hook 配置文件位置约定 | src/vs/platform/agentPlugins/common/pluginParsers.ts |
| 多来源发现行为测试 | src/vs/platform/agentHost/test/node/sessionCustomizationDiscovery.test.ts |
| 多根工作区主目录固定逻辑注释 | src/vs/platform/agentHost/common/state/sessionState.ts |
| Claude 兼容性(hookSpecificOutput 等) | src/vs/platform/agentHost/node/claude/claudeSdkOptions.ts |
适用前提说明:本文以当前 VS Code 仓库(包含 Copilot agent host 与定制技能包)的实际实现为准。VS Code 格式仅支持 8 个 PascalCase 事件;SessionEnd、ErrorOccurred 及 camelCase 事件名属于 GitHub Copilot CLI 格式,仅在带 version 字段的配置文件中生效。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00