Skip to main content

Hooks 参考

CSC hook 事件、配置模式、JSON 输入/输出格式、退出码、异步 hooks、HTTP hooks、提示 hooks 和 MCP 工具 hooks 的参考文档。

💡 提示: 有关快速入门指南和示例,请参阅使用 hooks 自动化工作流。

Hooks 是用户定义的 shell 命令、HTTP 端点或 LLM 提示,它们在 CSC 生命周期的特定点自动执行。使用此参考文档查找事件模式、配置选项、JSON 输入/输出格式以及异步 hooks、HTTP hooks 和 MCP 工具 hooks 等高级功能。如果你是首次设置 hooks,请先从指南开始。

Hook 生命周期

Hooks 在 CSC 会话的特定点触发。当事件触发且匹配器匹配时,CSC 将有关事件的 JSON 上下文传递给你的 hook 处理程序。对于命令 hooks,输入通过 stdin 到达。对于 HTTP hooks,它作为 POST 请求体到达。你的处理程序随后可以检查输入、采取行动,并可选地返回决策。事件分为三种频率:每个会话一次(SessionStartSessionEnd),每个轮次一次(UserPromptSubmitStopStopFailure),以及智能体循环中的每次工具调用(PreToolUsePostToolUse):

下表总结了每个事件的触发时机。Hook 事件部分记录了每个事件的完整输入模式和决策控制选项。

事件触发时机
SessionStart当会话开始或恢复时
UserPromptSubmit当你提交提示时,在 CSC 处理它之前
PreToolUse在工具调用执行之前。可以阻止它
PermissionRequest当权限对话框出现时
PermissionDenied当工具调用被自动模式分类器拒绝时。返回 {retry: true} 告知模型可以重试被拒绝的工具调用
PostToolUse在工具调用成功之后
PostToolUseFailure在工具调用失败之后
Notification当 CSC 发送通知时
SubagentStart当子代理被创建时
SubagentStop当子代理完成时
TaskCreated当通过 TaskCreate 创建任务时
TaskCompleted当任务被标记为完成时
Stop当 CSC 完成响应时
StopFailure当轮次因 API 错误而结束时。输出和退出码被忽略
TeammateIdle当代理团队的队友即将进入空闲状态时
InstructionsLoaded当 AGENTS.md 或 .costrict/rules/*.md 文件被加载到上下文中时。在会话开始时以及文件在会话期间被延迟加载时触发
ConfigChange当配置文件在会话期间更改时
CwdChanged当工作目录更改时,例如当 CSC 执行 cd 命令时。适用于与 direnv 等工具的响应式环境管理
FileChanged当监视的文件在磁盘上更改时。matcher 字段指定要监视的文件名
WorktreeCreate当通过 --worktreeisolation: "worktree" 创建工作树时。替换默认的 git 行为
WorktreeRemove当工作树被移除时,要么在会话退出时,要么在子代理完成时
PreCompact在上下文压缩之前
PostCompact在上下文压缩完成之后
Elicitation当 MCP 服务器在工具调用期间请求用户输入时
ElicitationResult在用户响应 MCP 请求后,响应发送回服务器之前
SessionEnd当会话终止时

Hook 如何解析

要了解这些部分如何组合在一起,请考虑这个阻止破坏性 shell 命令的 PreToolUse hook。matcher 缩小到 Bash 工具调用,if 条件进一步缩小到以 rm 开头的命令,因此 block-rm.sh 仅在两个过滤器都匹配时才会启动:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.costrict/hooks/block-rm.sh"
}
]
}
]
}
}

脚本从 stdin 读取 JSON 输入,提取命令,如果包含 rm -rf 则返回 "deny"permissionDecision

#!/bin/bash
# .costrict/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0 # allow the command
fi

现在假设 CSC 决定运行 Bash "rm -rf /tmp/build"。以下是发生的事情:

1. 事件触发

PreToolUse 事件触发。CSC 通过 stdin 将工具输入作为 JSON 发送给 hook:

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }

2. 匹配器检查

匹配器 "Bash" 匹配工具名称,因此此 hook 组被激活。如果省略匹配器或使用 "*",则该组在事件的每次发生时都会被激活。

3. If 条件检查

if 条件 "Bash(rm *)" 匹配,因为命令以 rm 开头,因此此处理程序启动。如果命令是 npm testif 检查将失败,block-rm.sh 将永远不会运行,从而避免了进程启动开销。if 字段是可选的;没有它,匹配组中的每个处理程序都会运行。

4. Hook 处理程序运行

脚本检查完整命令并找到 rm -rf,因此它将决策输出到 stdout:

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}

如果命令是更安全的 rm 变体,如 rm file.txt,脚本将执行 exit 0,这告诉 CSC 允许工具调用,无需进一步操作。

5. CSC 根据结果采取行动

CSC 读取 JSON 决策,阻止工具调用,并向 CSC 显示原因。

下面的配置部分记录了完整的模式,每个 hook 事件部分记录了你的命令接收什么输入以及可以返回什么输出。

配置

Hooks 定义在 JSON 设置文件中。配置有三个层级的嵌套:

  1. 选择一个要响应的 hook 事件,如 PreToolUseStop
  2. 添加一个匹配器组来过滤何时触发,如"仅用于 Bash 工具"
  3. 定义一个或多个在匹配时运行的 hook 处理程序

请参阅上面的 Hook 如何解析以获取带有注释示例的完整演练。

注意: 本页面对每个层级使用特定术语:hook 事件指生命周期点,匹配器组指过滤器,hook 处理程序指运行的 shell 命令、HTTP 端点、提示或代理。"Hook" 本身指的是通用功能。

Hook 位置

你在哪里定义 hook 决定了它的作用范围:

位置作用范围可共享
~/.costrict/settings.json你的所有项目否,仅限本机
.costrict/settings.json单个项目是,可以提交到仓库
.costrict/settings.local.json单个项目否,被 gitignore
托管策略设置组织范围是,由管理员控制
插件 hooks/hooks.json当插件启用时是,随插件打包
技能或代理 frontmatter当组件处于活动状态时是,定义在组件文件中

有关设置文件解析的详细信息,请参阅设置。企业管理员可以使用 allowManagedHooksOnly 来阻止用户、项目和插件 hooks。在托管设置 enabledPlugins 中强制启用的插件 hooks 豁免,因此管理员可以通过组织市场分发经过审查的 hooks。请参阅 Hook 配置。

匹配器模式

matcher 字段过滤 hooks 何时触发。匹配器的评估方式取决于它包含的字符:

匹配器值评估为示例
"*""" 或省略匹配所有在事件的每次发生时触发
仅包含字母、数字、_|精确字符串,或 | 分隔的精确字符串列表Bash 仅匹配 Bash 工具;Edit|Write 精确匹配任一工具
包含任何其他字符JavaScript 正则表达式^Notebook 匹配任何以 Notebook 开头的工具;mcp__memory__.* 匹配 memory 服务器的每个工具

FileChanged 事件在构建其监视列表时不遵循这些规则。请参阅 FileChanged。

每个事件类型匹配不同的字段:

事件匹配器过滤的内容匹配器值示例
PreToolUsePostToolUsePostToolUseFailurePermissionRequestPermissionDenied工具名称BashEdit|Writemcp__.*
SessionStart会话如何启动startupresumeclearcompact
SessionEnd会话为何结束clearresumelogoutprompt_input_exitbypass_permissions_disabledother
Notification通知类型permission_promptidle_promptauth_successelicitation_dialog
SubagentStart代理类型BashExplorePlan 或自定义代理名称
PreCompactPostCompact什么触发了压缩manualauto
SubagentStop代理类型SubagentStart 相同的值
ConfigChange配置来源user_settingsproject_settingslocal_settingspolicy_settingsskills
CwdChanged不支持匹配器每次目录更改时都触发
FileChanged要监视的字面文件名(请参阅 FileChanged).envrc|.env
StopFailure错误类型rate_limitauthentication_failedbilling_errorinvalid_requestserver_errormax_output_tokensunknown
InstructionsLoaded加载原因session_startnested_traversalpath_glob_matchincludecompact
ElicitationMCP 服务器名称你配置的 MCP 服务器名称
ElicitationResultMCP 服务器名称Elicitation 相同的值
UserPromptSubmitStopTeammateIdleTaskCreatedTaskCompletedWorktreeCreateWorktreeRemove不支持匹配器每次发生时都触发

匹配器针对 CSC 通过 stdin 发送给你的 hook 的 JSON 输入中的字段运行。对于工具事件,该字段是 tool_name。每个 hook 事件部分列出了该事件的完整匹配器值集和输入模式。

此示例仅在 CSC 写入或编辑文件时运行 lint 脚本:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/path/to/lint-check.sh"
}
]
}
]
}
}

UserPromptSubmitStopTeammateIdleTaskCreatedTaskCompletedWorktreeCreateWorktreeRemoveCwdChanged 不支持匹配器,每次发生时都会触发。如果你为这些事件添加 matcher 字段,它会被静默忽略。

对于工具事件,你可以通过在单个 hook 处理程序上设置 if 字段来更精确地过滤。if 使用权限规则语法来匹配工具名称和参数,因此 "Bash(git *)" 仅对 git 命令运行,"Edit(*.ts)" 仅对 TypeScript 文件运行。

匹配 MCP 工具

MCP 服务器工具在工具事件(PreToolUsePostToolUsePostToolUseFailurePermissionRequestPermissionDenied)中显示为常规工具,因此你可以像匹配任何其他工具名称一样匹配它们。

MCP 工具遵循命名模式 mcp__<server>__<tool>,例如:

  • mcp__memory__create_entities:Memory 服务器的创建实体工具
  • mcp__filesystem__read_file:Filesystem 服务器的读取文件工具
  • mcp__github__search_repositories:GitHub 服务器的搜索工具

要匹配服务器中的每个工具,请在服务器前缀后追加 .*.* 是必需的:像 mcp__memory 这样的匹配器仅包含字母和下划线,因此它作为精确字符串进行比较,不匹配任何工具。

  • mcp__memory__.* 匹配 memory 服务器的所有工具
  • mcp__.*__write.* 匹配任何服务器中名称以 write 开头的任何工具

此示例记录所有 memory 服务器操作并验证来自任何 MCP 服务器的写入操作:

{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__memory__.*",
"hooks": [
{
"type": "command",
"command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
}
]
},
{
"matcher": "mcp__.*__write.*",
"hooks": [
{
"type": "command",
"command": "/home/user/scripts/validate-mcp-write.py"
}
]
}
]
}
}

Hook 处理程序字段

内部 hooks 数组中的每个对象都是一个 hook 处理程序:当匹配器匹配时运行的 shell 命令、HTTP 端点、LLM 提示或代理。有四种类型:

  • 命令 hookstype: "command"):运行 shell 命令。你的脚本通过 stdin 接收事件的 JSON 输入,并通过退出码和 stdout 传回结果。
  • HTTP hookstype: "http"):将事件的 JSON 输入作为 HTTP POST 请求发送到 URL。端点使用与命令 hooks 相同的 JSON 输出格式通过响应体传回结果。
  • 提示 hookstype: "prompt"):向 CSC 模型发送提示进行单轮评估。模型以 JSON 返回是/否决策。请参阅基于提示的 hooks。
  • 代理 hookstype: "agent"):生成可以使用 Read、Grep 和 Glob 等工具的子代理,在返回决策之前验证条件。请参阅基于代理的 hooks。

通用字段

这些字段适用于所有 hook 类型:

字段必需描述
type"command""http""prompt""agent"
if权限规则语法,用于过滤此 hook 何时运行,如 "Bash(git *)""Edit(*.ts)"。hook 仅在工具调用匹配模式时才启动。仅在工具事件上评估:PreToolUsePostToolUsePostToolUseFailurePermissionRequestPermissionDenied。在其他事件上,设置了 if 的 hook 永远不会运行。使用与权限规则相同的语法
timeout取消前的秒数。默认值:命令 600,提示 30,代理 60
statusMessagehook 运行时显示的自定义加载消息
once如果为 true,每个会话仅运行一次然后被移除。仅限技能,不适用于代理。请参阅技能和代理中的 hooks

命令 hook 字段

除通用字段外,命令 hooks 还接受这些字段:

字段必需描述
command要执行的 shell 命令
async如果为 true,在后台运行而不阻塞。请参阅在后台运行 hooks
shell用于此 hook 的 shell。接受 "bash"(默认)或 "powershell"。设置 "powershell" 在 Windows 上通过 PowerShell 运行命令。不需要 CLAUDE_CODE_USE_POWERSHELL_TOOL,因为 hooks 直接启动 PowerShell

HTTP hook 字段

除通用字段外,HTTP hooks 还接受这些字段:

字段必需描述
url发送 POST 请求的 URL
headers作为键值对的额外 HTTP 头。值支持使用 $VAR_NAME${VAR_NAME} 语法的环境变量插值。仅解析 allowedEnvVars 中列出的变量
allowedEnvVars可以插入到头值中的环境变量名称列表。对未列出变量的引用被替换为空字符串。任何环境变量插值都需要此项才能工作

CSC 将 hook 的 JSON 输入作为带有 Content-Type: application/json 的 POST 请求体发送。响应体使用与命令 hooks 相同的 JSON 输出格式。

错误处理与命令 hooks 不同:非 2xx 响应、连接失败和超时都会产生非阻塞错误,允许执行继续。要阻止工具调用或拒绝权限,请返回 2xx 响应,其 JSON 体包含 decision: "block" 或带有 permissionDecision: "deny"hookSpecificOutput

此示例将 PreToolUse 事件发送到本地验证服务,使用 MY_TOKEN 环境变量中的令牌进行身份验证:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}

提示和代理 hook 字段

除通用字段外,提示和代理 hooks 还接受这些字段:

字段必需描述
prompt发送给模型的提示文本。使用 $ARGUMENTS 作为 hook 输入 JSON 的占位符
model用于评估的模型。默认为快速模型

所有匹配的 hooks 并行运行,相同的处理程序会自动去重。命令 hooks 按命令字符串去重,HTTP hooks 按 URL 去重。处理程序在当前目录中使用 CSC 的环境运行。$CLAUDE_CODE_REMOTE 环境变量在远程 Web 环境中设置为 "true",在本地 CLI 中未设置。

通过路径引用脚本

使用环境变量相对于项目或插件根目录引用 hook 脚本,无论 hook 运行时的工作目录如何:

  • $CLAUDE_PROJECT_DIR:项目根目录。用引号包裹以处理包含空格的路径。
  • ${CLAUDE_PLUGIN_ROOT}:插件的安装目录,用于随插件打包的脚本。每次插件更新时更改。
  • ${CLAUDE_PLUGIN_DATA}:插件的持久数据目录,用于应在插件更新后保留的依赖和状态。

项目脚本

此示例使用 $CLAUDE_PROJECT_DIR 在任何 WriteEdit 工具调用后从项目的 .costrict/hooks/ 目录运行样式检查器:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.costrict/hooks/check-style.sh"
}
]
}
]
}
}

插件脚本

hooks/hooks.json 中定义插件 hooks,带有可选的顶级 description 字段。当插件启用时,其 hooks 会与你的用户和项目 hooks 合并。

此示例运行随插件打包的格式化脚本:

{
"description": "Automatic code formatting",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
"timeout": 30
}
]
}
]
}
}

有关创建插件 hooks 的详细信息,请参阅插件组件参考。

技能和代理中的 Hooks

除了设置文件和插件外,hooks 还可以直接在使用 frontmatter 的技能和子代理中定义。这些 hooks 的作用范围限定在组件的生命周期内,仅在该组件处于活动状态时运行。

支持所有 hook 事件。对于子代理,Stop hooks 会自动转换为 SubagentStop,因为那是子代理完成时触发的事件。

Hooks 使用与基于设置的 hooks 相同的配置格式,但作用范围限定在组件的生命周期内,并在组件完成时清理。

此技能定义了一个 PreToolUse hook,在每个 Bash 命令之前运行安全验证脚本:

---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---

代理在其 YAML frontmatter 中使用相同的格式。

/hooks 菜单

在 CSC 中输入 /hooks 以打开配置 hooks 的只读浏览器。菜单显示每个 hook 事件及其已配置 hooks 的计数,让你可以深入查看匹配器,并显示每个 hook 处理程序的完整详细信息。使用它来验证配置、检查 hook 来自哪个设置文件,或检查 hook 的命令、提示或 URL。

菜单显示所有四种 hook 类型:commandpromptagenthttp。每个 hook 都标有 [type] 前缀和指示其定义位置的来源:

  • User:来自 ~/.costrict/settings.json
  • Project:来自 .costrict/settings.json
  • Local:来自 .costrict/settings.local.json
  • Plugin:来自插件的 hooks/hooks.json
  • Session:在当前会话的内存中注册
  • Built-in:由 CSC 内部注册

选择一个 hook 会打开一个详细视图,显示其事件、匹配器、类型、源文件以及完整的命令、提示或 URL。菜单是只读的:要添加、修改或移除 hooks,请直接编辑设置 JSON 或要求 CSC 进行更改。

禁用或移除 hooks

要移除 hook,请从设置 JSON 文件中删除其条目。

要在不移除的情况下临时禁用所有 hooks,请在设置文件中设置 "disableAllHooks": true。无法在保留配置中的单个 hook 的同时禁用它。

disableAllHooks 设置遵循托管设置的层级结构。如果管理员已通过托管策略设置配置了 hooks,则在用户、项目或本地设置中设置的 disableAllHooks 无法禁用那些托管 hooks。只有在托管设置级别设置的 disableAllHooks 才能禁用托管 hooks。

对设置文件中 hooks 的直接编辑通常会由文件监视器自动检测。

Hook 输入和输出

命令 hooks 通过 stdin 接收 JSON 数据,并通过退出码、stdout 和 stderr 传回结果。HTTP hooks 接收相同的 JSON 作为 POST 请求体,并通过 HTTP 响应体传回结果。本节涵盖所有事件共有的字段和行为。Hook 事件下每个事件的部分包括其特定的输入模式和决策控制选项。

通用输入字段

Hook 事件除了每个 hook 事件部分中记录的事件特定字段外,还接收这些字段作为 JSON。对于命令 hooks,此 JSON 通过 stdin 到达。对于 HTTP hooks,它作为 POST 请求体到达。

字段描述
session_id当前会话标识符
transcript_path对话 JSON 的路径
cwdhook 被调用时的当前工作目录
permission_mode当前权限模式:"default""plan""acceptEdits""auto""dontAsk""bypassPermissions"。并非所有事件都接收此字段:请查看下面每个事件的 JSON 示例来检查
hook_event_name触发的事件名称

当使用 --agent 运行或在子代理内部时,会包含两个额外的字段:

字段描述
agent_id子代理的唯一标识符。仅在 hook 在子代理调用内部触发时存在。使用它来区分子代理 hook 调用和主线程调用。
agent_type代理名称(例如,"Explore""security-reviewer")。当会话使用 --agent 或 hook 在子代理内部触发时存在。对于子代理,子代理的类型优先于会话的 --agent 值。

例如,Bash 命令的 PreToolUse hook 通过 stdin 接收以下内容:

{
"session_id": "abc123",
"transcript_path": "/home/user/.costrict/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}

tool_nametool_input 字段是事件特定的。每个 hook 事件部分记录了该事件的额外字段。

退出码输出

hook 命令的退出码告诉 CSC 操作是否应继续、被阻止或被忽略。

退出码 0 表示成功。CSC 解析 stdout 以获取 JSON 输出字段。JSON 输出仅在退出码 0 时处理。对于大多数事件,stdout 写入调试日志但不显示在记录中。例外是 UserPromptSubmitSessionStart,其中 stdout 作为 CSC 可以看到并据此行动的上下文添加。

退出码 2 表示阻塞错误。CSC 忽略 stdout 及其中的任何 JSON。相反,stderr 文本作为错误消息反馈给 CSC。效果取决于事件:PreToolUse 阻止工具调用,UserPromptSubmit 拒绝提示,等等。请参阅每个事件的退出码 2 行为以获取完整列表。

任何其他退出码 对于大多数 hook 事件是非阻塞错误。记录中显示 <hook name> hook error 通知,后跟 stderr 的第一行,因此你可以在不使用 --debug 的情况下识别原因。执行继续,完整的 stderr 写入调试日志。

例如,一个阻止危险 Bash 命令的 hook 命令脚本:

#!/bin/bash
# Reads JSON input from stdin, checks the command
command=$(jq -r '.tool_input.command' < /dev/stdin)

if [[ "$command" == rm* ]]; then
echo "Blocked: rm commands are not allowed" >&2
exit 2 # Blocking error: tool call is prevented
fi

exit 0 # Success: tool call proceeds

⚠️ 警告: 对于大多数 hook 事件,只有退出码 2 会阻止操作。CSC 将退出码 1 视为非阻塞错误并继续执行操作,即使 1 是传统的 Unix 失败码。如果你的 hook 旨在强制执行策略,请使用 exit 2。例外是 WorktreeCreate,其中任何非零退出码都会中止工作树创建。

每个事件的退出码 2 行为

退出码 2 是 hook 发出"停止,不要这样做"信号的方式。效果取决于事件,因为有些事件代表可以被阻止的操作(如尚未发生的工具调用),而其他事件代表已经发生或无法阻止的事情。

Hook 事件可以阻止?退出码 2 时发生什么
PreToolUse阻止工具调用
PermissionRequest拒绝权限
UserPromptSubmit阻止提示处理并清除提示
Stop阻止 CSC 停止,继续对话
SubagentStop阻止子代理停止
TeammateIdle阻止队友进入空闲状态(队友继续工作)
TaskCreated回滚任务创建
TaskCompleted阻止任务被标记为完成
ConfigChange阻止配置更改生效(policy_settings 除外)
StopFailure输出和退出码被忽略
PostToolUse向 CSC 显示 stderr(工具已运行)
PostToolUseFailure向 CSC 显示 stderr(工具已失败)
PermissionDenied退出码和 stderr 被忽略(拒绝已发生)。使用 JSON hookSpecificOutput.retry: true 告知模型可以重试
Notification仅向用户显示 stderr
SubagentStart仅向用户显示 stderr
SessionStart仅向用户显示 stderr
SessionEnd仅向用户显示 stderr
CwdChanged仅向用户显示 stderr
FileChanged仅向用户显示 stderr
PreCompact仅向用户显示 stderr
PostCompact仅向用户显示 stderr
Elicitation拒绝请求
ElicitationResult阻止响应(操作变为拒绝)
WorktreeCreate任何非零退出码都会导致工作树创建失败
WorktreeRemove失败仅在调试模式下记录
InstructionsLoaded退出码被忽略

HTTP 响应处理

HTTP hooks 使用 HTTP 状态码和响应体代替退出码和 stdout:

  • 2xx 且响应体为空:成功,等同于退出码 0 且无输出
  • 2xx 且响应体为纯文本:成功,文本作为上下文添加
  • 2xx 且响应体为 JSON:成功,使用与命令 hooks 相同的 JSON 输出模式解析
  • 非 2xx 状态:非阻塞错误,执行继续
  • 连接失败或超时:非阻塞错误,执行继续

与命令 hooks 不同,HTTP hooks 无法仅通过状态码发出阻塞错误信号。要阻止工具调用或拒绝权限,请返回 2xx 响应,其 JSON 体包含适当的决策字段。

JSON 输出

退出码让你允许或阻止,但 JSON 输出给你更细粒度的控制。与其以退出码 2 退出来阻止,不如退出码 0 并将 JSON 对象打印到 stdout。CSC 从该 JSON 中读取特定字段来控制行为,包括用于阻止、允许或升级给用户的决策控制。

注意: 你必须为每个 hook 选择一种方法,而不是两者兼用:要么仅使用退出码进行信号传递,要么退出码 0 并打印 JSON 进行结构化控制。CSC 仅在退出码 0 时处理 JSON。如果你退出码 2,任何 JSON 都会被忽略。

你的 hook 的 stdout 必须仅包含 JSON 对象。如果你的 shell 配置文件在启动时打印文本,它可能会干扰 JSON 解析。请参阅故障排除指南中的 JSON 验证失败。

注入到上下文中的 hook 输出(additionalContextsystemMessage 或纯 stdout)上限为 10,000 个字符。超过此限制的输出会保存到文件并替换为预览和文件路径,与大型工具结果的处理方式相同。

JSON 对象支持三种字段:

  • 通用字段continue 适用于所有事件。这些列在下表中。
  • 顶级 decisionreason 被某些事件用于阻止或提供反馈。
  • hookSpecificOutput 是需要更丰富控制的事件的嵌套对象。它需要一个设置为事件名称的 hookEventName 字段。
字段默认值描述
continuetrue如果为 false,CSC 在 hook 运行后完全停止处理。优先于任何事件特定的决策字段
stopReasoncontinuefalse 时向用户显示的消息。不向 CSC 显示
suppressOutputfalse如果为 true,从调试日志中省略 stdout
systemMessage向用户显示的警告消息

要完全停止 CSC,无论事件类型如何:

{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }

决策控制

并非每个事件都支持通过 JSON 阻止或控制行为。支持的事件各自使用不同的字段集来表达该决策。在编写 hook 之前,使用此表作为快速参考:

事件决策模式关键字段
UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange顶级 decisiondecision: "block"reason
TeammateIdle, TaskCreated, TaskCompleted退出码或 continue: false退出码 2 通过 stderr 反馈阻止操作。JSON {"continue": false, "stopReason": "..."} 也会完全停止队友,匹配 Stop hook 行为
PreToolUsehookSpecificOutputpermissionDecision(allow/deny/ask/defer)、permissionDecisionReason
PermissionRequesthookSpecificOutputdecision.behavior(allow/deny)
PermissionDeniedhookSpecificOutputretry: true 告知模型可以重试被拒绝的工具调用
WorktreeCreate路径返回命令 hook 在 stdout 上打印路径;HTTP hook 返回 hookSpecificOutput.worktreePath。Hook 失败或缺少路径会导致创建失败
ElicitationhookSpecificOutputaction(accept/decline/cancel)、content(accept 的表单字段值)
ElicitationResulthookSpecificOutputaction(accept/decline/cancel)、content(表单字段值覆盖)
WorktreeRemove, Notification, SessionEnd, PreCompact, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged无决策控制。用于日志记录或清理等副作用

以下是每种模式的实际示例:

顶级 decision

用于 UserPromptSubmitPostToolUsePostToolUseFailureStopSubagentStopConfigChange。唯一的值是 "block"。要允许操作继续,请从 JSON 中省略 decision,或者退出码 0 且不带任何 JSON:

{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}

PreToolUse

使用 hookSpecificOutput 进行更丰富的控制:允许、拒绝或升级给用户。你还可以在工具运行之前修改工具输入或为 CSC 注入额外的上下文。请参阅 PreToolUse 决策控制以获取完整的选项集。

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database writes are not allowed"
}
}

PermissionRequest

使用 hookSpecificOutput 代表用户允许或拒绝权限请求。允许时,你还可以修改工具的输入或应用权限规则,这样用户就不会再次被提示。请参阅 PermissionRequest 决策控制以获取完整的选项集。

{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}

有关扩展示例,包括 Bash 命令验证、提示过滤和自动批准脚本,请参阅指南中的你可以自动化什么以及 Bash 命令验证器参考实现。

Hook 事件

每个事件对应 CSC 生命周期中可以运行 hooks 的一个点。以下部分按生命周期顺序排列:从会话设置到智能体循环再到会话结束。每个部分描述事件何时触发、支持哪些匹配器、接收什么 JSON 输入,以及如何通过输出控制行为。

SessionStart

当 CSC 开始新会话或恢复现有会话时运行。适用于加载开发上下文,如现有问题或代码库的最近更改,或设置环境变量。对于不需要脚本的静态上下文,请改用 AGENTS.md。

SessionStart 在每个会话上运行,因此请保持这些 hooks 快速。仅支持 type: "command" hooks。

匹配器值对应于会话的启动方式:

匹配器触发时机
startup新会话
resume--resume--continue/resume
clear/clear
compact自动或手动压缩

SessionStart 输入

除通用输入字段外,SessionStart hooks 接收 sourcemodel 和可选的 agent_typesource 字段指示会话如何启动:"startup" 表示新会话,"resume" 表示恢复的会话,"clear" 表示 /clear 之后,"compact" 表示压缩之后。model 字段包含模型标识符。如果你使用 csc --agent <name> 启动 CSC,agent_type 字段包含代理名称。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SessionStart",
"source": "startup",
"model": "claude-sonnet-4-6"
}

SessionStart 决策控制

你的 hook 脚本打印到 stdout 的任何文本都会作为 CSC 的上下文添加。除了所有 hooks 可用的 JSON 输出字段外,你还可以返回这些事件特定的字段:

字段描述
additionalContext添加到 CSC 上下文的字符串。多个 hooks 的值会连接起来
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "My additional context here"
}
}

持久化环境变量

SessionStart hooks 可以访问 CLAUDE_ENV_FILE 环境变量,它提供了一个文件路径,你可以在其中为后续 Bash 命令持久化环境变量。

要设置单个环境变量,请将 export 语句写入 CLAUDE_ENV_FILE。使用追加(>>)来保留其他 hooks 设置的变量:

#!/bin/bash

if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi

exit 0

要从设置命令中捕获所有环境更改,请比较之前和之后的导出变量:

#!/bin/bash

ENV_BEFORE=$(export -p | sort)

# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20

if [ -n "$CLAUDE_ENV_FILE" ]; then
ENV_AFTER=$(export -p | sort)
comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi

exit 0

写入此文件的任何变量将在 CSC 在会话期间执行的所有后续 Bash 命令中可用。

注意: CLAUDE_ENV_FILE 可用于 SessionStart、CwdChanged 和 FileChanged hooks。其他 hook 类型无法访问此变量。

InstructionsLoaded

AGENTS.md.costrict/rules/*.md 文件被加载到上下文中时触发。此事件在会话开始时为急切加载的文件触发,稍后当文件被延迟加载时再次触发,例如当 CSC 访问包含嵌套 AGENTS.md 的子目录或具有 paths: frontmatter 的条件规则匹配时。该 hook 不支持阻止或决策控制。它出于可观察性目的异步运行。

匹配器针对 load_reason 运行。例如,使用 "matcher": "session_start" 仅在会话开始时为文件触发,或使用 "matcher": "path_glob_match|nested_traversal" 仅在延迟加载时触发。

InstructionsLoaded 输入

除通用输入字段外,InstructionsLoaded hooks 接收这些字段:

字段描述
file_path被加载的指令文件的绝对路径
memory_type文件的范围:"User""Project""Local""Managed"
load_reason文件被加载的原因:"session_start""nested_traversal""path_glob_match""include""compact""compact" 值在压缩事件后重新加载指令文件时触发
globs文件 paths: frontmatter 中的路径 glob 模式(如果有)。仅在 path_glob_match 加载时存在
trigger_file_path触发此加载的文件访问路径,用于延迟加载
parent_file_path包含此文件的父指令文件路径,用于 include 加载
{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "InstructionsLoaded",
"file_path": "/Users/my-project/AGENTS.md",
"memory_type": "Project",
"load_reason": "session_start"
}

InstructionsLoaded 决策控制

InstructionsLoaded hooks 没有决策控制。它们无法阻止或修改指令加载。使用此事件进行审计日志记录、合规跟踪或可观察性。

UserPromptSubmit

当用户提交提示时运行,在 CSC 处理它之前。这允许你根据提示/对话添加额外的上下文、验证提示或阻止某些类型的提示。

UserPromptSubmit 输入

除通用输入字段外,UserPromptSubmit hooks 接收包含用户提交文本的 prompt 字段。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "UserPromptSubmit",
"prompt": "Write a function to calculate the factorial of a number"
}

UserPromptSubmit 决策控制

UserPromptSubmit hooks 可以控制用户提示是否被处理并添加上下文。所有 JSON 输出字段都可用。

在退出码 0 时有两种方法可以向对话添加上下文:

  • 纯文本 stdout:写入 stdout 的任何非 JSON 文本都作为上下文添加
  • 带有 additionalContext 的 JSON:使用下面的 JSON 格式进行更多控制。additionalContext 字段作为上下文添加

纯 stdout 在记录中显示为 hook 输出。additionalContext 字段更隐蔽地添加。

要阻止提示,请返回 decision 设置为 "block" 的 JSON 对象:

字段描述
decision"block" 阻止提示被处理并将其从上下文中删除。省略以允许提示继续
reasondecision"block" 时向用户显示。不添加到上下文
additionalContext添加到 CSC 上下文的字符串
sessionTitle设置会话标题,与 /rename 效果相同。用于根据提示内容自动命名会话
{
"decision": "block",
"reason": "Explanation for decision",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "My additional context here",
"sessionTitle": "My session title"
}
}

注意: JSON 格式对于简单用例不是必需的。要添加上下文,你可以在退出码 0 时将纯文本打印到 stdout。当你需要阻止提示或想要更结构化的控制时使用 JSON。

PreToolUse

在 CSC 创建工具参数之后、处理工具调用之前运行。按工具名称匹配:BashEditWriteReadGlobGrepAgentWebFetchWebSearchAskUserQuestionExitPlanMode 以及任何 MCP 工具名称。

使用 PreToolUse 决策控制来允许、拒绝、询问或延迟工具调用。

PreToolUse 输入

除通用输入字段外,PreToolUse hooks 接收 tool_nametool_inputtool_use_idtool_input 字段取决于工具:

Bash

执行 shell 命令。

字段类型示例描述
commandstring"npm test"要执行的 shell 命令
descriptionstring"Run test suite"命令功能的可选描述
timeoutnumber120000可选的超时时间(毫秒)
run_in_backgroundbooleanfalse是否在后台运行命令
Write

创建或覆盖文件。

字段类型示例描述
file_pathstring"/path/to/file.txt"要写入的文件的绝对路径
contentstring"file content"要写入文件的内容
Edit

替换现有文件中的字符串。

字段类型示例描述
file_pathstring"/path/to/file.txt"要编辑的文件的绝对路径
old_stringstring"original text"要查找和替换的文本
new_stringstring"replacement text"替换文本
replace_allbooleanfalse是否替换所有出现
Read

读取文件内容。

字段类型示例描述
file_pathstring"/path/to/file.txt"要读取的文件的绝对路径
offsetnumber10可选的起始读取行号
limitnumber50可选的读取行数
Glob

查找匹配 glob 模式的文件。

字段类型示例描述
patternstring"**/*.ts"用于匹配文件的 glob 模式
pathstring"/path/to/dir"可选的搜索目录。默认为当前工作目录
Grep

使用正则表达式搜索文件内容。

字段类型示例描述
patternstring"TODO.*fix"要搜索的正则表达式模式
pathstring"/path/to/dir"可选的搜索文件或目录
globstring"*.ts"可选的用于过滤文件的 glob 模式
output_modestring"content""content""files_with_matches""count"。默认为 "files_with_matches"
-ibooleantrue不区分大小写搜索
multilinebooleanfalse启用多行匹配
WebFetch

获取并处理 Web 内容。

字段类型示例描述
urlstring"https://example.com/api"获取内容的 URL
promptstring"Extract the API endpoints"对获取的内容运行的提示
WebSearch

搜索 Web。

字段类型示例描述
querystring"react hooks best practices"搜索查询
allowed_domainsarray["docs.example.com"]可选:仅包含来自这些域的结果
blocked_domainsarray["spam.example.com"]可选:排除来自这些域的结果
Agent

生成子代理。

字段类型示例描述
promptstring"Find all API endpoints"代理要执行的任务
descriptionstring"Find API endpoints"任务的简短描述
subagent_typestring"Explore"要使用的专业代理类型
modelstring"sonnet"可选的模型别名以覆盖默认值
AskUserQuestion

向用户提出一到四个多选题。

字段类型示例描述
questionsarray[{"question": "Which framework?", "header": "Framework", "options": [{"label": "React"}], "multiSelect": false}]要呈现的问题,每个问题包含 question 字符串、简短的 headeroptions 数组和可选的 multiSelect 标志
answersobject{"Which framework?": "React"}可选。将问题文本映射到选定的选项标签。多选答案用逗号连接标签。CSC 不会设置此字段;通过 updatedInput 提供它以编程方式回答

PreToolUse 决策控制

PreToolUse hooks 可以控制工具调用是否继续。与使用顶级 decision 字段的其他 hooks 不同,PreToolUse 在 hookSpecificOutput 对象内返回其决策。这给了它更丰富的控制:四种结果(允许、拒绝、询问或延迟)加上在执行之前修改工具输入的能力。

字段描述
permissionDecision"allow" 跳过权限提示。"deny" 阻止工具调用。"ask" 提示用户确认。"defer" 优雅退出以便稍后恢复工具。当 hook 返回 "allow" 时,拒绝和询问规则仍然适用
permissionDecisionReason对于 "allow""ask",向用户显示但不向 CSC 显示。对于 "deny",向 CSC 显示。对于 "defer",忽略
updatedInput在执行之前修改工具的输入参数。替换整个输入对象,因此请包含未更改的字段以及修改后的字段。与 "allow" 结合以自动批准,或与 "ask" 结合以向用户显示修改后的输入。对于 "defer",忽略
additionalContext在工具执行之前添加到 CSC 上下文的字符串。对于 "defer",忽略

当多个 PreToolUse hooks 返回不同的决策时,优先级为 deny > defer > ask > allow

当 hook 返回 "ask" 时,向用户显示的权限提示包含标识 hook 来源的标签:例如,[User][Project][Plugin][Local]。这有助于用户了解哪个配置源正在请求确认。

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "My reason here",
"updatedInput": {
"field_to_modify": "new value"
},
"additionalContext": "Current environment: production. Proceed with caution."
}
}

AskUserQuestionExitPlanMode 需要用户交互,通常在使用 -p 标志的非交互模式下会阻塞。返回 permissionDecision: "allow" 加上 updatedInput 可以满足该要求:hook 从 stdin 读取工具的输入,通过你自己的 UI 收集答案,并在 updatedInput 中返回它,这样工具无需提示即可运行。仅返回 "allow" 对这些工具是不够的。对于 AskUserQuestion,回显原始的 questions 数组并添加一个 answers 对象,将每个问题的文本映射到所选答案。

注意: PreToolUse 以前使用顶级的 decisionreason 字段,但这些在此事件中已弃用。请改用 hookSpecificOutput.permissionDecisionhookSpecificOutput.permissionDecisionReason。已弃用的值 "approve""block" 分别映射到 "allow""deny"。其他事件如 PostToolUse 和 Stop 继续使用顶级 decisionreason 作为其当前格式。

延迟工具调用以便稍后处理

"defer" 适用于将 csc -p 作为子进程运行并读取其 JSON 输出的集成,例如 Agent SDK 应用程序或基于 CSC 构建的自定义 UI。它让调用进程可以在工具调用处暂停 CSC,通过自己的界面收集输入,并从停下的地方恢复。CSC 仅在使用 -p 标志的非交互模式下接受此值。在交互会话中,它会记录警告并忽略 hook 结果。

注意: defer 值需要 CSC v2.1.89 或更高版本。早期版本无法识别它,工具会通过正常的权限流程继续。

AskUserQuestion 工具是典型情况:CSC 想问用户一些事情,但没有终端可以回答。往返过程如下:

  1. CSC 调用 AskUserQuestionPreToolUse hook 触发。
  2. hook 返回 permissionDecision: "defer"。工具不执行。进程以 stop_reason: "tool_deferred" 退出,待处理的工具调用保留在记录中。
  3. 调用进程从 SDK 结果中读取 deferred_tool_use,在其自己的 UI 中显示问题,并等待答案。
  4. 调用进程运行 csc -p --resume <session-id>。相同的工具调用再次触发 PreToolUse
  5. hook 返回 permissionDecision: "allow" 并在 updatedInput 中附带答案。工具执行,CSC 继续。

deferred_tool_use 字段携带工具的 idnameinputinput 是 CSC 为工具调用生成的参数,在执行之前捕获:

{
"type": "result",
"subtype": "success",
"stop_reason": "tool_deferred",
"session_id": "abc123",
"deferred_tool_use": {
"id": "toolu_01abc",
"name": "AskUserQuestion",
"input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
}
}

没有超时或重试限制。会话保留在磁盘上,直到你恢复它。如果恢复时答案还没准备好,hook 可以再次返回 "defer",进程以相同方式退出。调用进程通过最终从 hook 返回 "allow""deny" 来控制何时打破循环。

"defer" 仅在 CSC 在轮次中发出单个工具调用时有效。如果 CSC 同时发出多个工具调用,"defer" 会被忽略并显示警告,工具通过正常的权限流程继续。存在此约束是因为恢复只能重新运行一个工具:没有办法从一批调用中延迟一个而不让其他调用未解决。

如果延迟的工具在你恢复时不再可用,进程会在 hook 触发之前以 stop_reason: "tool_deferred_unavailable"is_error: true 退出。当提供该工具的 MCP 服务器未为恢复的会话连接时会发生这种情况。deferred_tool_use 负载仍会包含,以便你识别哪个工具缺失了。

⚠️ 警告: --resume 不会从之前的会话恢复权限模式。在恢复时传递与工具被延迟时活动的相同的 --permission-mode 标志。如果模式不同,CSC 会记录警告。

PermissionRequest

当用户看到权限对话框时运行。使用 PermissionRequest 决策控制代表用户允许或拒绝。

按工具名称匹配,与 PreToolUse 相同的值。

PermissionRequest 输入

PermissionRequest hooks 接收与 PreToolUse hooks 相同的 tool_nametool_input 字段,但没有 tool_use_id。可选的 permission_suggestions 数组包含用户通常在权限对话框中看到的"始终允许"选项。区别在于 hook 何时触发:PermissionRequest hooks 在权限对话框即将显示给用户时运行,而 PreToolUse hooks 在工具执行之前运行,无论权限状态如何。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PermissionRequest",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules",
"description": "Remove node_modules directory"
},
"permission_suggestions": [
{
"type": "addRules",
"rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
"behavior": "allow",
"destination": "localSettings"
}
]
}

PermissionRequest 决策控制

PermissionRequest hooks 可以允许或拒绝权限请求。除了所有 hooks 可用的 JSON 输出字段外,你的 hook 脚本可以返回带有这些事件特定字段的 decision 对象:

字段描述
behavior"allow" 授予权限,"deny" 拒绝权限
updatedInput仅用于 "allow":在执行之前修改工具的输入参数。替换整个输入对象,因此请包含未更改的字段以及修改后的字段
updatedPermissions仅用于 "allow":要应用的权限更新条目数组,例如添加允许规则或更改会话权限模式
message仅用于 "deny":告诉 CSC 为什么权限被拒绝
interrupt仅用于 "deny":如果为 true,停止 CSC
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}

权限更新条目

updatedPermissions 输出字段和 permission_suggestions 输入字段都使用相同的条目对象数组。每个条目有一个 type 来决定其其他字段,以及一个 destination 来控制更改写入的位置。

type字段效果
addRulesrulesbehaviordestination添加权限规则。rules{toolName, ruleContent?} 对象的数组。省略 ruleContent 以匹配整个工具。behavior"allow""deny""ask"
replaceRulesrulesbehaviordestination用提供的 rules 替换 destination 处给定 behavior 的所有规则
removeRulesrulesbehaviordestination移除给定 behavior 的匹配规则
setModemodedestination更改权限模式。有效模式为 defaultacceptEditsdontAskbypassPermissionsplan
addDirectoriesdirectoriesdestination添加工作目录。directories 是路径字符串数组
removeDirectoriesdirectoriesdestination移除工作目录

每个条目上的 destination 字段决定更改是保留在内存中还是持久化到设置文件。

destination写入位置
session仅在内存中,会话结束时丢弃
localSettings.costrict/settings.local.json
projectSettings.costrict/settings.json
userSettings~/.costrict/settings.json

hook 可以将其接收的 permission_suggestions 之一作为自己的 updatedPermissions 输出回显,这等同于用户在对话框中选择该"始终允许"选项。

PostToolUse

在工具成功完成后立即运行。

按工具名称匹配,与 PreToolUse 相同的值。

PostToolUse 输入

PostToolUse hooks 在工具已经成功执行后触发。输入包括 tool_input(发送给工具的参数)和 tool_response(它返回的结果)。两者的确切模式取决于工具。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.txt",
"content": "file content"
},
"tool_response": {
"filePath": "/path/to/file.txt",
"success": true
},
"tool_use_id": "toolu_01ABC123..."
}

PostToolUse 决策控制

PostToolUse hooks 可以在工具执行后向 CSC 提供反馈。除了所有 hooks 可用的 JSON 输出字段外,你的 hook 脚本可以返回这些事件特定的字段:

字段描述
decision"block"reason 提示 CSC。省略以允许操作继续
reasondecision"block" 时向 CSC 显示的解释
additionalContext供 CSC 考虑的额外上下文
updatedMCPToolOutput仅用于 MCP 工具:用提供的值替换工具的输出
{
"decision": "block",
"reason": "Explanation for decision",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Additional information for CSC"
}
}

PostToolUseFailure

当工具执行失败时运行。此事件在抛出错误或返回失败结果的工具调用时触发。使用它来记录失败、发送警报或向 CSC 提供纠正反馈。

按工具名称匹配,与 PreToolUse 相同的值。

PostToolUseFailure 输入

PostToolUseFailure hooks 接收与 PostToolUse 相同的 tool_nametool_input 字段,以及作为顶级字段的错误信息:

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolUseFailure",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite"
},
"tool_use_id": "toolu_01ABC123...",
"error": "Command exited with non-zero status code 1",
"is_interrupt": false
}
字段描述
error描述出了什么问题的字符串
is_interrupt可选布尔值,指示失败是否由用户中断引起

PostToolUseFailure 决策控制

PostToolUseFailure hooks 可以在工具失败后向 CSC 提供上下文。除了所有 hooks 可用的 JSON 输出字段外,你的 hook 脚本可以返回这些事件特定的字段:

字段描述
additionalContext供 CSC 与错误一起考虑的额外上下文
{
"hookSpecificOutput": {
"hookEventName": "PostToolUseFailure",
"additionalContext": "Additional information about the failure for CSC"
}
}

PermissionDenied

当自动模式分类器拒绝工具调用时运行。此 hook 仅在自动模式下触发:当你手动拒绝权限对话框、当 PreToolUse hook 阻止调用或当 deny 规则匹配时,它不会运行。使用它来记录分类器拒绝、调整配置或告知模型可以重试工具调用。

按工具名称匹配,与 PreToolUse 相同的值。

PermissionDenied 输入

除通用输入字段外,PermissionDenied hooks 接收 tool_nametool_inputtool_use_idreason

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "auto",
"hook_event_name": "PermissionDenied",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/build",
"description": "Clean build directory"
},
"tool_use_id": "toolu_01ABC123...",
"reason": "Auto mode denied: command targets a path outside the project"
}
字段描述
reason分类器对工具调用为何被拒绝的解释

PermissionDenied 决策控制

PermissionDenied hooks 可以告知模型可以重试被拒绝的工具调用。返回一个 hookSpecificOutput.retry 设置为 true 的 JSON 对象:

{
"hookSpecificOutput": {
"hookEventName": "PermissionDenied",
"retry": true
}
}

retrytrue 时,CSC 向对话添加一条消息,告知模型可以重试工具调用。拒绝本身不会被撤回。如果你的 hook 不返回 JSON,或返回 retry: false,拒绝仍然有效,模型收到原始拒绝消息。

Notification

当 CSC 发送通知时运行。按通知类型匹配:permission_promptidle_prompt、auth_successelicitation_dialog`。省略匹配器以为所有通知类型运行 hooks。

使用单独的匹配器根据通知类型运行不同的处理程序。此配置在 CSC 需要权限批准时触发特定于权限的警报脚本,在 CSC 空闲时触发不同的通知:

{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "/path/to/permission-alert.sh"
}
]
},
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "/path/to/idle-notification.sh"
}
]
}
]
}
}

Notification 输入

除通用输入字段外,Notification hooks 接收带有通知文本的 message、可选的 title 以及指示触发了哪种类型的 notification_type

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Notification",
"message": "CSC needs your permission to use Bash",
"title": "Permission needed",
"notification_type": "permission_prompt"
}

Notification hooks 无法阻止或修改通知。除了所有 hooks 可用的 JSON 输出字段外,你可以返回 additionalContext 以向对话添加上下文:

字段描述
additionalContext添加到 CSC 上下文的字符串

SubagentStart

当通过 Agent 工具生成 CSC 子代理时运行。支持匹配器按代理类型名称过滤(内置代理如 BashExplorePlan,或来自 .costrict/agents/ 的自定义代理名称)。

SubagentStart 输入

除通用输入字段外,SubagentStart hooks 接收带有子代理唯一标识符的 agent_id 和带有代理名称的 agent_type(内置代理如 "Bash""Explore""Plan",或自定义代理名称)。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SubagentStart",
"agent_id": "agent-abc123",
"agent_type": "Explore"
}

SubagentStart hooks 无法阻止子代理创建,但它们可以向子代理注入上下文。除了所有 hooks 可用的 JSON 输出字段外,你可以返回:

字段描述
additionalContext添加到子代理上下文的字符串
{
"hookSpecificOutput": {
"hookEventName": "SubagentStart",
"additionalContext": "Follow security guidelines for this task"
}
}

SubagentStop

当 CSC 子代理完成响应时运行。按代理类型匹配,与 SubagentStart 相同的值。

SubagentStop 输入

除通用输入字段外,SubagentStop hooks 接收 stop_hook_activeagent_idagent_typeagent_transcript_pathlast_assistant_messageagent_type 字段是用于匹配器过滤的值。transcript_path 是主会话的记录,而 agent_transcript_path 是存储在嵌套 subagents/ 文件夹中的子代理自己的记录。last_assistant_message 字段包含子代理最终响应的文本内容,因此 hooks 可以访问它而无需解析记录文件。

{
"session_id": "abc123",
"transcript_path": "~/.costrict/projects/.../abc123.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "SubagentStop",
"stop_hook_active": false,
"agent_id": "def456",
"agent_type": "Explore",
"agent_transcript_path": "~/.costrict/projects/.../abc123/subagents/agent-def456.jsonl",
"last_assistant_message": "Analysis complete. Found 3 potential issues..."
}

SubagentStop hooks 使用与 Stop hooks 相同的决策控制格式。

TaskCreated

当通过 TaskCreate 工具创建任务时运行。使用此来强制执行命名约定、要求任务描述或阻止创建某些任务。

TaskCreated hook 以退出码 2 退出时,任务不会被创建,stderr 消息作为反馈反馈给模型。要完全停止队友而不是重新运行它,请返回带有 {"continue": false, "stopReason": "..."} 的 JSON。TaskCreated hooks 不支持匹配器,每次发生时都会触发。

TaskCreated 输入

除通用输入字段外,TaskCreated hooks 接收 task_idtask_subject 和可选的 task_descriptionteammate_nameteam_name

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "TaskCreated",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints",
"teammate_name": "implementer",
"team_name": "my-project"
}
字段描述
task_id正在创建的任务的标识符
task_subject任务的标题
task_description任务的详细描述。可能不存在
teammate_name创建任务的队友名称。可能不存在
team_name团队名称。可能不存在

TaskCreated 决策控制

TaskCreated hooks 支持两种控制任务创建的方式:

  • 退出码 2:任务不会被创建,stderr 消息作为反馈反馈给模型。
  • JSON {"continue": false, "stopReason": "..."}:完全停止队友,匹配 Stop hook 行为。stopReason 向用户显示。

此示例阻止主题不符合所需格式的任务:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
exit 2
fi

exit 0

TaskCompleted

当任务被标记为完成时运行。这在两种情况下触发:当任何代理通过 TaskUpdate 工具明确将任务标记为完成时,或当代理团队队友在仍有进行中任务时完成其轮次时。使用此来在任务关闭之前强制执行完成标准,如通过测试或 lint 检查。

TaskCompleted hook 以退出码 2 退出时,任务不会被标记为完成,stderr 消息作为反馈反馈给模型。要完全停止队友而不是重新运行它,请返回带有 {"continue": false, "stopReason": "..."} 的 JSON。TaskCompleted hooks 不支持匹配器,每次发生时都会触发。

TaskCompleted 输入

除通用输入字段外,TaskCompleted hooks 接收 task_idtask_subject 和可选的 task_descriptionteammate_nameteam_name

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "TaskCompleted",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints",
"teammate_name": "implementer",
"team_name": "my-project"
}
字段描述
task_id正在完成的任务的标识符
task_subject任务的标题
task_description任务的详细描述。可能不存在
teammate_name完成任务的队友名称。可能不存在
team_name团队名称。可能不存在

TaskCompleted 决策控制

TaskCompleted hooks 支持两种控制任务完成的方式:

  • 退出码 2:任务不会被标记为完成,stderr 消息作为反馈反馈给模型。
  • JSON {"continue": false, "stopReason": "..."}:完全停止队友,匹配 Stop hook 行为。stopReason 向用户显示。

此示例运行测试并在测试失败时阻止任务完成:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

# Run the test suite
if ! npm test 2>&1; then
echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
exit 2
fi

exit 0

Stop

当主 CSC 代理完成响应时运行。如果停止是由于用户中断引起的,则不运行。API 错误会触发 StopFailure。

Stop 输入

除通用输入字段外,Stop hooks 接收 stop_hook_activelast_assistant_message。当 CSC 已经作为 stop hook 的结果继续时,stop_hook_active 字段为 true。检查此值或处理记录以防止 CSC 无限期运行。last_assistant_message 字段包含 CSC 最终响应的文本内容,因此 hooks 可以访问它而无需解析记录文件。

{
"session_id": "abc123",
"transcript_path": "~/.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "Stop",
"stop_hook_active": true,
"last_assistant_message": "I've completed the refactoring. Here's a summary..."
}

Stop 决策控制

StopSubagentStop hooks 可以控制 CSC 是否继续。除了所有 hooks 可用的 JSON 输出字段外,你的 hook 脚本可以返回这些事件特定的字段:

字段描述
decision"block" 阻止 CSC 停止。省略以允许 CSC 停止
reasondecision"block" 时必需。告诉 CSC 为什么应该继续
{
"decision": "block",
"reason": "Must be provided when CSC is blocked from stopping"
}

StopFailure

当轮次因 API 错误结束时代替 Stop 运行。输出和退出码被忽略。使用此来在 CSC 由于速率限制、身份验证问题或其他 API 错误无法完成响应时记录失败、发送警报或采取恢复操作。

StopFailure 输入

除通用输入字段外,StopFailure hooks 接收 error、可选的 error_details 和可选的 last_assistant_messageerror 字段标识错误类型,用于匹配器过滤。

字段描述
error错误类型:rate_limitauthentication_failedbilling_errorinvalid_requestserver_errormax_output_tokensunknown
error_details关于错误的额外详细信息(如果可用)
last_assistant_message对话中显示的渲染错误文本。与 StopSubagentStop 中此字段保存 CSC 的对话输出不同,对于 StopFailure,它包含 API 错误字符串本身,如 "API Error: Rate limit reached"
{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "StopFailure",
"error": "rate_limit",
"error_details": "429 Too Many Requests",
"last_assistant_message": "API Error: Rate limit reached"
}

StopFailure hooks 没有决策控制。它们仅用于通知和日志记录目的。

TeammateIdle

当代理团队的队友完成其轮次后即将进入空闲状态时运行。使用此在队友停止工作之前强制执行质量门,如要求通过 lint 检查或验证输出文件是否存在。

TeammateIdle hook 以退出码 2 退出时,队友接收 stderr 消息作为反馈并继续工作而不是进入空闲状态。要完全停止队友而不是重新运行它,请返回带有 {"continue": false, "stopReason": "..."} 的 JSON。TeammateIdle hooks 不支持匹配器,每次发生时都会触发。

TeammateIdle 输入

除通用输入字段外,TeammateIdle hooks 接收 teammate_nameteam_name

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "TeammateIdle",
"teammate_name": "researcher",
"team_name": "my-project"
}
字段描述
teammate_name即将进入空闲状态的队友名称
team_name团队名称

TeammateIdle 决策控制

TeammateIdle hooks 支持两种控制队友行为的方式:

  • 退出码 2:队友接收 stderr 消息作为反馈并继续工作而不是进入空闲状态。
  • JSON {"continue": false, "stopReason": "..."}:完全停止队友,匹配 Stop hook 行为。stopReason 向用户显示。

此示例在允许队友进入空闲状态之前检查构建产物是否存在:

#!/bin/bash

if [ ! -f "./dist/output.js" ]; then
echo "Build artifact missing. Run the build before stopping." >&2
exit 2
fi

exit 0

ConfigChange

当配置文件在会话期间更改时运行。使用此来审计设置更改、强制执行安全策略或阻止对配置文件的未授权修改。

ConfigChange hooks 在设置文件、托管策略设置和技能文件更改时触发。输入中的 source 字段告诉你哪种类型的配置发生了更改,可选的 file_path 字段提供更改文件的路径。

匹配器按配置来源过滤:

匹配器触发时机
user_settings~/.costrict/settings.json 更改
project_settings.costrict/settings.json 更改
local_settings.costrict/settings.local.json 更改
policy_settings托管策略设置更改
skills.costrict/skills/ 中的技能文件更改

此示例记录所有配置更改以进行安全审计:

{
"hooks": {
"ConfigChange": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.costrict/hooks/audit-config-change.sh"
}
]
}
]
}
}

ConfigChange 输入

除通用输入字段外,ConfigChange hooks 接收 source 和可选的 file_pathsource 字段指示哪种配置类型发生了更改,file_path 提供被修改的特定文件的路径。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "ConfigChange",
"source": "project_settings",
"file_path": "/Users/.../my-project/.costrict/settings.json"
}

ConfigChange 决策控制

ConfigChange hooks 可以阻止配置更改生效。使用退出码 2 或 JSON decision 来阻止更改。被阻止时,新设置不会应用到正在运行的会话。

字段描述
decision"block" 阻止配置更改被应用。省略以允许更改
reasondecision"block" 时向用户显示的解释
{
"decision": "block",
"reason": "Configuration changes to project settings require admin approval"
}

policy_settings 更改无法被阻止。Hooks 仍然会为 policy_settings 来源触发,因此你可以使用它们进行审计日志记录,但任何阻止决策都会被忽略。这确保了企业管理的设置始终生效。

CwdChanged

当工作目录在会话期间更改时运行,例如当 CSC 执行 cd 命令时。使用此来响应目录更改:重新加载环境变量、激活项目特定的工具链或自动运行设置脚本。与 FileChanged 配合使用,用于管理每个目录环境的 direnv 等工具。

CwdChanged hooks 可以访问 CLAUDE_ENV_FILE。写入该文件的变量持久化到会话的后续 Bash 命令中,就像 SessionStart hooks 中一样。仅支持 type: "command" hooks。

CwdChanged 不支持匹配器,每次目录更改时都会触发。

CwdChanged 输入

除通用输入字段外,CwdChanged hooks 接收 old_cwdnew_cwd

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../transcript.jsonl",
"cwd": "/Users/my-project/src",
"hook_event_name": "CwdChanged",
"old_cwd": "/Users/my-project",
"new_cwd": "/Users/my-project/src"
}

CwdChanged 输出

除了所有 hooks 可用的 JSON 输出字段外,CwdChanged hooks 可以返回 watchPaths 来动态设置 FileChanged 监视哪些文件路径:

字段描述
watchPaths绝对路径数组。替换当前的动态监视列表(来自你的 matcher 配置的路径总是被监视)。返回空数组会清除动态列表,这在进入新目录时是典型的

CwdChanged hooks 没有决策控制。它们无法阻止目录更改。

FileChanged

当监视的文件在磁盘上更改时运行。适用于在项目配置文件被修改时重新加载环境变量。

此事件的 matcher 有两个作用:

  • 构建监视列表:值按 | 分割,每个段在工作目录中注册为字面文件名,因此 ".envrc|.env" 精确监视这两个文件。正则表达式模式在这里没有用:像 ^\.env 这样的值会监视一个名为 ^\.env 的文件。
  • 过滤哪些 hooks 运行:当监视的文件更改时,相同的值使用标准匹配器规则针对更改文件的基本名称过滤哪些 hook 组运行。

FileChanged hooks 可以访问 CLAUDE_ENV_FILE。写入该文件的变量持久化到会话的后续 Bash 命令中,就像 SessionStart hooks 中一样。仅支持 type: "command" hooks。

FileChanged 输入

除通用输入字段外,FileChanged hooks 接收 file_pathevent

字段描述
file_path更改的文件的绝对路径
event发生了什么:"change"(文件被修改)、"add"(文件被创建)或 "unlink"(文件被删除)
{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "FileChanged",
"file_path": "/Users/my-project/.envrc",
"event": "change"
}

FileChanged 输出

除了所有 hooks 可用的 JSON 输出字段外,FileChanged hooks 可以返回 watchPaths 来动态更新监视哪些文件路径:

字段描述
watchPaths绝对路径数组。替换当前的动态监视列表(来自你的 matcher 配置的路径总是被监视)。当你的 hook 脚本基于更改的文件发现要监视的额外文件时使用此功能

FileChanged hooks 没有决策控制。它们无法阻止文件更改的发生。

WorktreeCreate

当你运行 csc --worktree 或子代理使用 isolation: "worktree" 时,CSC 使用 git worktree 创建一个隔离的工作副本。如果你配置了 WorktreeCreate hook,它会替换默认的 git 行为,让你可以使用不同的版本控制系统,如 SVN、Perforce 或 Mercurial。

由于 hook 完全替换了默认行为,.worktreeinclude 不会被处理。如果你需要将 .env 等本地配置文件复制到新的工作树中,请在 hook 脚本内部完成。

hook 必须返回创建的工作树目录的绝对路径。CSC 使用此路径作为隔离会话的工作目录。命令 hooks 在 stdout 上打印它;HTTP hooks 通过 hookSpecificOutput.worktreePath 返回它。

此示例创建一个 SVN 工作副本并打印路径供 CSC 使用。将仓库 URL 替换为你自己的:

{
"hooks": {
"WorktreeCreate": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.costrict/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
}
]
}
]
}
}

hook 从 stdin 上的 JSON 输入中读取工作树 name,将新副本检出到新目录中,并打印目录路径。最后一行的 echo 是 CSC 读取为工作树路径的内容。将任何其他输出重定向到 stderr,以免干扰路径。

WorktreeCreate 输入

除通用输入字段外,WorktreeCreate hooks 接收 name 字段。这是新工作树的 slug 标识符,由用户指定或自动生成(例如,bold-oak-a3f2)。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeCreate",
"name": "feature-auth"
}

WorktreeCreate 输出

WorktreeCreate hooks 不使用标准的允许/阻止决策模型。相反,hook 的成功或失败决定了结果。hook 必须返回创建的工作树目录的绝对路径:

  • 命令 hookstype: "command"):在 stdout 上打印路径。
  • HTTP hookstype: "http"):在响应体中返回 { "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }

如果 hook 失败或未产生路径,工作树创建将失败并显示错误。

WorktreeRemove

WorktreeCreate 的清理对应项。此 hook 在工作树被移除时触发,要么在你退出 --worktree 会话并选择移除它时,要么在具有 isolation: "worktree" 的子代理完成时。对于基于 git 的工作树,CSC 使用 git worktree remove 自动处理清理。如果你为非 git 版本控制系统配置了 WorktreeCreate hook,请配对一个 WorktreeRemove hook 来处理清理。没有的话,工作树目录将留在磁盘上。

CSC 将 WorktreeCreate 返回的路径作为 hook 输入中的 worktree_path 传递。此示例读取该路径并移除目录:

{
"hooks": {
"WorktreeRemove": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
}
]
}
]
}
}

WorktreeRemove 输入

除通用输入字段外,WorktreeRemove hooks 接收 worktree_path 字段,即被移除的工作树的绝对路径。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeRemove",
"worktree_path": "/Users/.../my-project/.costrict/worktrees/feature-auth"
}

WorktreeRemove hooks 没有决策控制。它们无法阻止工作树移除,但可以执行清理任务,如移除版本控制状态或归档更改。Hook 失败仅在调试模式下记录。

PreCompact

在 CSC 即将运行压缩操作之前运行。

匹配器值指示压缩是手动触发还是自动触发的:

匹配器触发时机
manual/compact
auto当上下文窗口已满时自动压缩

PreCompact 输入

除通用输入字段外,PreCompact hooks 接收 triggercustom_instructions。对于 manualcustom_instructions 包含用户传入 /compact 的内容。对于 autocustom_instructions 为空。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "PreCompact",
"trigger": "manual",
"custom_instructions": ""
}

PostCompact

在 CSC 完成压缩操作后运行。使用此事件来响应新的压缩状态,例如记录生成的摘要或更新外部状态。

PreCompact 相同的匹配器值适用:

匹配器触发时机
manual/compact 之后
auto当上下文窗口已满时自动压缩之后

PostCompact 输入

除通用输入字段外,PostCompact hooks 接收 triggercompact_summarycompact_summary 字段包含由压缩操作生成的对话摘要。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "PostCompact",
"trigger": "manual",
"compact_summary": "Summary of the compacted conversation..."
}

PostCompact hooks 没有决策控制。它们无法影响压缩结果,但可以执行后续任务。

SessionEnd

当 CSC 会话结束时运行。适用于清理任务、记录会话统计信息或保存会话状态。支持匹配器按退出原因过滤。

hook 输入中的 reason 字段指示会话为何结束:

原因描述
clear使用 /clear 命令清除会话
resume通过交互式 /resume 切换会话
logout用户登出
prompt_input_exit提示输入可见时用户退出
bypass_permissions_disabled绕过权限模式被禁用
other其他退出原因

SessionEnd 输入

除通用输入字段外,SessionEnd hooks 接收指示会话为何结束的 reason 字段。请参阅上面的原因表以获取所有值。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SessionEnd",
"reason": "other"
}

SessionEnd hooks 没有决策控制。它们无法阻止会话终止,但可以执行清理任务。

SessionEnd hooks 的默认超时为 1.5 秒。这适用于会话退出、/clear 和通过交互式 /resume 切换会话。如果你的 hooks 需要更多时间,请将 CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS 环境变量设置为更高的毫秒值。任何每个 hook 的 timeout 设置也受此值限制。

CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 csc

Elicitation

当 MCP 服务器在任务中途请求用户输入时运行。默认情况下,CSC 显示一个交互式对话框供用户响应。Hooks 可以拦截此请求并以编程方式响应,完全跳过对话框。

匹配器字段针对 MCP 服务器名称匹配。

Elicitation 输入

除通用输入字段外,Elicitation hooks 接收 mcp_server_namemessage 和可选的 modeurlelicitation_idrequested_schema 字段。

对于表单模式的请求(最常见的情况):

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "Elicitation",
"mcp_server_name": "my-mcp-server",
"message": "Please provide your credentials",
"mode": "form",
"requested_schema": {
"type": "object",
"properties": {
"username": { "type": "string", "title": "Username" }
}
}
}

对于 URL 模式的请求(基于浏览器的身份验证):

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "Elicitation",
"mcp_server_name": "my-mcp-server",
"message": "Please authenticate",
"mode": "url",
"url": "https://auth.example.com/login"
}

Elicitation 输出

要以编程方式响应而不显示对话框,请返回带有 hookSpecificOutput 的 JSON 对象:

{
"hookSpecificOutput": {
"hookEventName": "Elicitation",
"action": "accept",
"content": {
"username": "alice"
}
}
}
字段描述
actionacceptdeclinecancel是接受、拒绝还是取消请求
contentobject要提交的表单字段值。仅在 actionaccept 时使用

退出码 2 拒绝请求并向用户显示 stderr。

ElicitationResult

在用户响应 MCP 请求后运行。Hooks 可以在响应发送回 MCP 服务器之前观察、修改或阻止响应。

匹配器字段针对 MCP 服务器名称匹配。

ElicitationResult 输入

除通用输入字段外,ElicitationResult hooks 接收 mcp_server_nameaction 和可选的 modeelicitation_idcontent 字段。

{
"session_id": "abc123",
"transcript_path": "/Users/.../.costrict/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "ElicitationResult",
"mcp_server_name": "my-mcp-server",
"action": "accept",
"content": { "username": "alice" },
"mode": "form",
"elicitation_id": "elicit-123"
}

ElicitationResult 输出

要覆盖用户的响应,请返回带有 hookSpecificOutput 的 JSON 对象:

{
"hookSpecificOutput": {
"hookEventName": "ElicitationResult",
"action": "decline",
"content": {}
}
}
字段描述
actionacceptdeclinecancel覆盖用户的操作
contentobject覆盖表单字段值。仅在 actionaccept 时有意义

退出码 2 阻止响应,将有效操作更改为 decline

基于提示的 Hooks

除了命令和 HTTP hooks 外,CSC 还支持基于提示的 hooks(type: "prompt"),使用 LLM 评估是否允许或阻止操作,以及代理 hooks(type: "agent"),生成带有工具访问权限的代理验证器。并非所有事件都支持每种 hook 类型。

支持所有四种 hook 类型(commandhttppromptagent)的事件:

  • PermissionRequest
  • PostToolUse
  • PostToolUseFailure
  • PreToolUse
  • Stop
  • SubagentStop
  • TaskCompleted
  • TaskCreated
  • UserPromptSubmit

支持 commandhttp hooks 但不支持 promptagent 的事件:

  • ConfigChange
  • CwdChanged
  • Elicitation
  • ElicitationResult
  • FileChanged
  • InstructionsLoaded
  • Notification
  • PermissionDenied
  • PostCompact
  • PreCompact
  • SessionEnd
  • StopFailure
  • SubagentStart
  • TeammateIdle
  • WorktreeCreate
  • WorktreeRemove

SessionStart 仅支持 command hooks。

基于提示的 Hooks 如何工作

与执行 Bash 命令不同,基于提示的 hooks:

  1. 将 hook 输入和你的提示发送给 CSC 模型,默认为 Haiku
  2. LLM 以包含决策的结构化 JSON 响应
  3. CSC 自动处理决策

提示 Hook 配置

type 设置为 "prompt" 并提供 prompt 字符串而不是 command。使用 $ARGUMENTS 占位符将 hook 的 JSON 输入数据注入到你的提示文本中。CSC 将组合的提示和输入发送给快速的 CSC 模型,后者返回 JSON 决策。

Stop hook 要求 LLM 在允许 CSC 完成之前评估所有任务是否完成:

{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if CSC should stop: $ARGUMENTS. Check if all tasks are complete."
}
]
}
]
}
}
字段必需描述
type必须为 "prompt"
prompt发送给 LLM 的提示文本。使用 $ARGUMENTS 作为 hook 输入 JSON 的占位符。如果 $ARGUMENTS 不存在,输入 JSON 会追加到提示
model用于评估的模型。默认为快速模型
timeout超时时间(秒)。默认:30

响应模式

LLM 必须以包含以下内容的 JSON 响应:

{
"ok": true | false,
"reason": "Explanation for the decision"
}
字段描述
oktrue 允许操作,false 阻止操作
reasonokfalse 时必需。向 CSC 显示的解释

示例:多条件 Stop hook

Stop hook 使用详细的提示在允许 CSC 停止之前检查三个条件。如果 "ok"false,CSC 继续工作,以提供的原因为下一步指令。SubagentStop hooks 使用相同的格式来评估子代理是否应该停止:

{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "You are evaluating whether CSC should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
"timeout": 30
}
]
}
]
}
}

基于代理的 Hooks

基于代理的 hooks(type: "agent")类似于基于提示的 hooks,但具有多轮工具访问权限。与单次 LLM 调用不同,代理 hook 生成一个子代理,可以读取文件、搜索代码和检查代码库来验证条件。代理 hooks 支持与基于提示的 hooks 相同的事件。

代理 Hooks 如何工作

当代理 hook 触发时:

  1. CSC 生成一个带有你的提示和 hook 的 JSON 输入的子代理
  2. 子代理可以使用 Read、Grep 和 Glob 等工具进行调查
  3. 最多 50 轮后,子代理返回结构化的 { "ok": true/false } 决策
  4. CSC 以与提示 hook 相同的方式处理决策

当验证需要检查实际文件或测试输出,而不仅仅是评估 hook 输入数据时,代理 hooks 很有用。

代理 Hook 配置

type 设置为 "agent" 并提供 prompt 字符串。配置字段与提示 hooks 相同,但默认超时更长:

字段必需描述
type必须为 "agent"
prompt描述要验证什么的提示。使用 $ARGUMENTS 作为 hook 输入 JSON 的占位符
model用于评估的模型。默认为快速模型
timeout超时时间(秒)。默认:60

响应模式与提示 hooks 相同:{ "ok": true } 允许或 { "ok": false, "reason": "..." } 阻止。

Stop hook 在允许 CSC 完成之前验证所有单元测试是否通过:

{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}

在后台运行 Hooks

默认情况下,hooks 会阻塞 CSC 的执行直到完成。对于长时间运行的任务,如部署、测试套件或外部 API 调用,设置 "async": true 以在 CSC 继续工作的同时在后台运行 hook。异步 hooks 无法阻止或控制 CSC 的行为:响应字段如 decisionpermissionDecisioncontinue 没有效果,因为它们本应控制的操作已经完成。

配置异步 Hook

在命令 hook 的配置中添加 "async": true 以在后台运行而不阻塞 CSC。此字段仅在 type: "command" hooks 上可用。

此 hook 在每次 Write 工具调用后运行测试脚本。CSC 立即继续工作,而 run-tests.sh 执行最多 120 秒。当脚本完成时,其输出在下一个对话轮次中传递:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "/path/to/run-tests.sh",
"async": true,
"timeout": 120
}
]
}
]
}
}

timeout 字段设置后台进程的最大时间(秒)。如果未指定,异步 hooks 使用与同步 hooks 相同的 10 分钟默认值。

异步 Hooks 如何执行

当异步 hook 触发时,CSC 启动 hook 进程并立即继续,不等待其完成。hook 通过 stdin 接收与同步 hook 相同的 JSON 输入。

后台进程退出后,如果 hook 生成了带有 systemMessageadditionalContext 字段的 JSON 响应,该内容将在下一个对话轮次中作为上下文传递给 CSC。

异步 hook 完成通知默认被抑制。要查看它们,请使用 Ctrl+O 启用详细模式或使用 --verbose 启动 CSC。

示例:文件更改后运行测试

此 hook 在 CSC 写入文件时在后台启动测试套件,然后在测试完成时将结果报告给 CSC。将此脚本保存到项目中的 .costrict/hooks/run-tests-async.sh 并使用 chmod +x 使其可执行:

#!/bin/bash
# run-tests-async.sh

# Read hook input from stdin
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Only run tests for source files
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
exit 0
fi

# Run tests and report results via systemMessage
RESULT=$(npm test 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"
else
echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"
fi

然后将此配置添加到项目根目录的 .costrict/settings.json 中。async: true 标志让 CSC 在测试运行时继续工作:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.costrict/hooks/run-tests-async.sh",
"async": true,
"timeout": 300
}
]
}
]
}
}

限制

异步 hooks 与同步 hooks 相比有几个约束:

  • 只有 type: "command" hooks 支持 async。基于提示的 hooks 无法异步运行。
  • 异步 hooks 无法阻止工具调用或返回决策。当 hook 完成时,触发操作已经继续。
  • Hook 输出在下一个对话轮次中传递。如果会话空闲,响应会等到下一次用户交互。
  • 每次执行都会创建一个单独的后台进程。同一个异步 hook 的多次触发之间没有去重。

安全注意事项

免责声明

命令 hooks 以你的系统用户的完整权限运行。

⚠️ 警告: 命令 hooks 以你的完整用户权限执行 shell 命令。它们可以修改、删除或访问你的用户帐户可以访问的任何文件。在将 hook 命令添加到配置之前,请审查并测试所有 hook 命令。

安全最佳实践

在编写 hooks 时请记住这些做法:

  • 验证和清理输入:永远不要盲目信任输入数据
  • 始终引用 shell 变量:使用 "$VAR" 而不是 $VAR
  • 阻止路径遍历:检查文件路径中的 ..
  • 使用绝对路径:为脚本指定完整路径,使用 "$CLAUDE_PROJECT_DIR" 作为项目根目录
  • 跳过敏感文件:避免 .env.git/、密钥等

Windows PowerShell 工具

在 Windows 上,你可以通过在命令 hook 上设置 "shell": "powershell" 来在 PowerShell 中运行单个 hooks。Hooks 直接启动 PowerShell,因此无论是否设置了 CLAUDE_CODE_USE_POWERSHELL_TOOL,这都可以工作。CSC 自动检测 pwsh.exe(PowerShell 7+),回退到 powershell.exe(5.1)。

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"shell": "powershell",
"command": "Write-Host 'File written'"
}
]
}
]
}
}

调试 Hooks

Hook 执行详细信息,包括哪些 hooks 匹配、它们的退出码以及完整的 stdout 和 stderr,都写入调试日志文件。使用 csc --debug-file <path> 启动 CSC 以将日志写入已知位置,或运行 csc --debug 并在 ~/.costrict/debug/<session-id>.txt 处读取日志。--debug 标志不会打印到终端。

[DEBUG] Executing hooks for PostToolUse:Write
[DEBUG] Found 1 hook commands to execute
[DEBUG] Executing hook command: <Your command> with timeout 600000ms
[DEBUG] Hook command completed with status 0: <Your stdout>

要获取更细粒度的 hook 匹配详细信息,请设置 CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose 以查看额外的日志行,如 hook 匹配器计数和查询匹配。

有关常见问题的故障排除,如 hooks 未触发、无限 Stop hook 循环或配置错误,请参阅指南中的限制和故障排除。