Skip to Content
用户指南功能特性钩子

Qwen Code 钩子

概述

Qwen Code 钩子提供了一种强大的机制,用于扩展和自定义 Qwen Code 应用程序的行为。钩子允许用户在应用程序生命周期的特定节点(如工具执行前、工具执行后、会话开始/结束以及其他关键事件期间)执行自定义脚本或程序。

钩子默认处于启用状态。你可以通过在设置文件(与 hooks 同级)中将 disableAllHooks 设置为 true 来临时禁用所有钩子:

{ "disableAllHooks": true, "hooks": { "PreToolUse": [...] } }

这会禁用所有钩子,但不会删除其配置。

什么是钩子?

钩子是由用户定义的脚本或程序,Qwen Code 会在应用程序流程的预定义节点自动执行它们。它们允许用户:

  • 监控和审计工具使用情况
  • 强制执行安全策略
  • 向对话中注入额外的上下文
  • 根据事件自定义应用程序行为
  • 与外部系统和服务集成
  • 以编程方式修改工具输入或响应

钩子类型

Qwen Code 支持四种钩子执行器类型:

类型描述
command执行 shell 命令。通过 stdin 接收 JSON,通过 stdout 返回结果。
http将 JSON 作为 POST 请求体发送到指定的 URL。通过 HTTP 响应体返回结果。
function直接调用已注册的 JavaScript 函数(仅限会话级钩子)。
prompt使用 LLM 评估钩子输入并返回决策。

命令钩子

命令钩子通过子进程执行命令。输入 JSON 通过 stdin 传递,输出通过 stdout 返回。

配置:

字段类型必填描述
type"command"钩子类型
commandstring要执行的命令
namestring钩子名称(用于日志记录)
descriptionstring钩子描述
timeoutnumber超时时间(毫秒),默认 60000
asyncboolean是否在后台异步运行
envRecord<string, string>环境变量
shell"bash" | "powershell"要使用的 Shell
statusMessagestring执行期间显示的状态消息

示例:

{ "hooks": { "PreToolUse": [ { "matcher": "WriteFile", "hooks": [ { "type": "command", "command": "$QWEN_PROJECT_DIR/.qwen/hooks/security-check.sh", "name": "security-check", "timeout": 10000 } ] } ] } }

HTTP 钩子

HTTP 钩子将钩子输入作为 POST 请求发送到指定的 URL。它们支持 URL 白名单、DNS 级 SSRF 防护、环境变量插值等安全特性。

配置:

字段类型必填描述
type"http"钩子类型
urlstring目标 URL
headersRecord<string, string>请求头(支持环境变量插值)
allowedEnvVarsstring[]URL/请求头中允许使用的环境变量白名单
timeoutnumber超时时间(秒),默认 600
namestring钩子名称(用于日志记录)
statusMessagestring执行期间显示的状态消息
onceboolean每个会话中每个事件仅执行一次(仅限 HTTP 钩子)

安全特性:

  • URL 白名单:通过 allowedUrls 配置允许的 URL 模式
  • SSRF 防护:拦截私有 IP(10.x.x.x、172.16-31.x.x、192.168.x.x 等),但允许环回地址(127.0.0.1、::1)
  • DNS 验证:在请求前验证域名解析,以防止 DNS 重绑定攻击
  • 环境变量插值:使用 ${VAR} 语法,仅允许 allowedEnvVars 白名单中的变量

示例:

{ "hooks": { "PreToolUse": [ { "matcher": "*", "hooks": [ { "type": "http", "url": "http://127.0.0.1:8080/hooks/pre-tool-use", "headers": { "Authorization": "Bearer ${HOOK_API_KEY}" }, "allowedEnvVars": ["HOOK_API_KEY"], "timeout": 10, "name": "remote-security-check" } ] } ] } }

函数钩子

函数钩子直接调用已注册的 JavaScript/TypeScript 函数。它们由 Skill 系统在内部使用,目前尚未作为公共 API 暴露给最终用户。

注意:对于大多数用例,请改用命令钩子HTTP 钩子,它们可以在设置文件中进行配置。

Prompt 钩子

Prompt 钩子使用 LLM 评估钩子输入并返回决策。这对于基于上下文做出智能决策非常有用,例如决定是否允许或阻止某个操作。

工作原理:

  1. 钩子输入 JSON 通过 $ARGUMENTS 占位符注入到你的 prompt 中
  2. prompt 被发送到 LLM(默认使用你当前的模型)
  3. LLM 返回包含决策结果的 JSON 响应
  4. Qwen Code 处理该决策,并相应地继续或阻止执行

配置:

字段类型必填描述
type"prompt"钩子类型
promptstring发送到 LLM 的 prompt。使用 $ARGUMENTS 获取钩子输入
modelstring要使用的模型(默认使用你当前的模型)
timeoutnumber超时时间(秒),默认 30
namestring钩子名称(用于日志记录)
descriptionstring钩子描述
statusMessagestring执行期间显示的状态消息

响应格式:

LLM 必须返回具有以下结构的 JSON:

{ "ok": true, "reason": "Explanation of the decision", "additionalContext": "Optional context to inject into the conversation" }
字段描述
oktrue 表示允许/继续,false 表示阻止/停止
reasonokfalse 时必填。显示给模型以解释阻止原因
additionalContext可选。允许操作时注入到对话中的额外上下文

支持的事件:

Prompt 钩子可用于大多数钩子事件,包括:

  • PreToolUse - 评估是否允许工具调用
  • PostToolUse - 评估工具结果并可能注入上下文
  • Stop - 决定是继续还是停止
  • SubagentStop - 评估子代理结果
  • UserPromptSubmit - 评估或丰富用户 prompt

示例:Stop 钩子

{ "hooks": { "Stop": [ { "hooks": [ { "type": "prompt", "prompt": "You are evaluating whether Qwen Code 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 } ] } ] } }

okfalse 时,Qwen Code 将继续工作,并使用 reason 作为下一次响应的上下文。

示例:PreToolUse 钩子

{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "prompt", "prompt": "Evaluate this tool call for security concerns. Tool input: $ARGUMENTS\n\nCheck for:\n- Dangerous commands (rm -rf, curl | sh, etc.)\n- Unauthorized access attempts\n- Data exfiltration patterns\n\nRespond with {\"ok\": true} if safe, or {\"ok\": false, \"reason\": \"concern\"} if blocked.", "model": "sonnet", "timeout": 30, "name": "security-evaluator" } ] } ] } }

钩子事件

钩子在 Qwen Code 会话的特定节点触发。不同的事件支持不同的 matcher 来过滤触发条件。

事件触发时机Matcher 目标
PreToolUse工具执行前工具名称(WriteFileReadFileBash 等)
PostToolUse工具成功执行后工具名称
PostToolUseFailure工具执行失败后工具名称
UserPromptSubmit用户提交 prompt 后无(始终触发)
SessionStart会话开始或恢复时来源(startupresumeclearcompact
SessionEnd会话结束时原因(clearlogoutprompt_input_exit 等)
Stop当 Claude 准备结束响应时无(始终触发)
SubagentStart子代理启动时代理类型(BashExplorerPlan 等)
SubagentStop子代理停止时代理类型
PreCompact对话压缩前触发器(manualauto
Notification发送通知时类型(permission_promptidle_promptauth_success
PermissionRequest显示权限对话框时工具名称
TodoCreated创建新的 todo 项时无(始终触发)
TodoCompletedtodo 项被标记为已完成时无(始终触发)

匹配器模式

matcher 是一个用于过滤触发条件的正则表达式。

事件类型事件匹配器支持匹配目标
工具事件PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest✅ 正则表达式工具名称:WriteFileReadFileBash
子代理事件SubagentStart, SubagentStop✅ 正则表达式代理类型:BashExplorer
会话事件SessionStart✅ 正则表达式来源:startupresumeclearcompact
会话事件SessionEnd✅ 正则表达式原因:clearlogoutprompt_input_exit
通知事件Notification✅ 精确匹配类型:permission_promptidle_promptauth_success
Compact 事件PreCompact✅ 精确匹配触发方式:manualauto
Todo 事件TodoCreated, TodoCompleted❌ 不支持N/A
提示词事件UserPromptSubmit❌ 不支持N/A
停止事件Stop❌ 不支持N/A

匹配器语法:

  • 空字符串 """*" 匹配该类型的所有事件
  • 支持标准正则表达式语法(例如,^Bash$Read.*(WriteFile|Edit)

示例:

{ "hooks": { "PreToolUse": [ { "matcher": "^Bash$", "hooks": [ { "type": "command", "command": "echo 'bash check' >> /tmp/hooks.log" } ] }, { "matcher": "Write.*", "hooks": [ { "type": "command", "command": "echo 'write check' >> /tmp/hooks.log" } ] }, { "matcher": "*", "hooks": [ { "type": "command", "command": "echo 'all tools' >> /tmp/hooks.log" } ] } ], "SubagentStart": [ { "matcher": "^(Bash|Explorer)$", "hooks": [ { "type": "command", "command": "echo 'subagent check' >> /tmp/hooks.log" } ] } ] } }

输入/输出规则

Hook 输入结构

所有 Hook 都通过 stdin(command)或 POST body(http)接收 JSON 格式的标准输入。

通用字段:

{ "session_id": "string", "transcript_path": "string", "cwd": "string", "hook_event_name": "string", "timestamp": "string" }

根据 Hook 类型添加特定于事件的字段。在子代理中运行时,还会额外包含 agent_idagent_type

Hook 输出结构

Hook 输出通过 stdout(command)或 HTTP 响应体(http)以 JSON 格式返回。

退出码行为(Command Hooks):

退出码行为
0成功。解析 stdout 中的 JSON 以控制行为。
2阻塞错误。忽略 stdout,将 stderr 作为错误反馈传递给模型。
其他非阻塞错误。stderr 仅在调试模式下显示,继续执行。

输出结构:

Hook 输出支持三类字段:

  1. 通用字段continuestopReasonsuppressOutputsystemMessage
  2. 顶层决策decisionreason(部分事件使用)
  3. 事件特定控制hookSpecificOutput(必须包含 hookEventName
{ "continue": true, "decision": "allow", "reason": "Operation approved", "hookSpecificOutput": { "hookEventName": "PreToolUse", "additionalContext": "Additional context information" } }

各 Hook 事件详情

PreToolUse

用途:在使用工具之前执行,用于进行权限检查、输入验证或上下文注入。

事件特定字段

{ "permission_mode": "default | plan | auto_edit | yolo", "tool_name": "name of the tool being executed", "tool_input": "object containing the tool's input parameters", "tool_use_id": "unique identifier for this tool use instance (internal format, e.g., toolu_xxx)", "tool_call_id": "original API call ID from the LLM provider (e.g., call_xxx for OpenAI/Qwen) (optional)" }

输出选项

  • hookSpecificOutput.permissionDecision:“allow”、“deny” 或 “ask”(必填)
  • hookSpecificOutput.permissionDecisionReason:决策原因(必填)
  • hookSpecificOutput.updatedInput:修改后的工具输入参数,用于替代原始参数
  • hookSpecificOutput.additionalContext:额外的上下文信息

permissionDecision 的值控制工具是否运行:

  • "allow" — 运行工具,无需通常的批准提示。
  • "deny" — 阻止工具;工具不会执行,并向模型返回错误。
  • "ask" — 暂停并在 TUI 中要求用户确认工具调用,然后再运行。确认则运行一次工具;拒绝则取消。在无法提示确认的上下文中(如无头 --prompt 运行和后台子代理),"ask" 会回退到 "deny"

注意:虽然底层类在技术上支持 decisionreason 等标准 Hook 输出字段,但官方接口期望使用包含 permissionDecisionpermissionDecisionReasonhookSpecificOutput

输出示例

{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Security policy blocks database writes", "additionalContext": "Current environment: production. Proceed with caution." } }

PostToolUse

用途:在工具成功完成后执行,用于处理结果、记录结果或注入额外的上下文。

事件特定字段

{ "permission_mode": "default | plan | auto_edit | yolo", "tool_name": "name of the tool that was executed", "tool_input": "object containing the tool's input parameters", "tool_response": "object containing the tool's response", "tool_use_id": "unique identifier for this tool use instance (internal format, e.g., toolu_xxx)", "tool_call_id": "original API call ID from the LLM provider (e.g., call_xxx for OpenAI/Qwen) (optional)" }

输出选项

  • decision:“allow”、“deny”、“block”(如未指定则默认为 “allow”)
  • reason:决策原因
  • hookSpecificOutput.additionalContext:要包含的额外信息

输出示例

{ "decision": "allow", "reason": "Tool executed successfully", "hookSpecificOutput": { "additionalContext": "File modification recorded in audit log" } }

PostToolUseFailure

用途:在工具执行失败时执行,用于处理错误、发送警报或记录失败。

事件特定字段

{ "permission_mode": "default | plan | auto_edit | yolo", "tool_use_id": "unique identifier for the tool use (internal format, e.g., toolu_xxx)", "tool_call_id": "original API call ID from the LLM provider (e.g., call_xxx for OpenAI/Qwen) (optional)", "tool_name": "name of the tool that failed", "tool_input": "object containing the tool's input parameters", "error": "error message describing the failure", "is_interrupt": "boolean indicating if failure was due to user interruption (optional)" }

输出选项

  • hookSpecificOutput.additionalContext:错误处理信息
  • 标准 Hook 输出字段

输出示例

{ "hookSpecificOutput": { "additionalContext": "Error: File not found. Failure logged in monitoring system." } }

UserPromptSubmit

用途:在用户提交提示词时执行,用于修改、验证或丰富输入。

事件特定字段

{ "prompt": "the user's submitted prompt text" }

输出选项

  • decision:“allow”、“deny”、“block” 或 “ask”
  • reason:决策的人类可读解释
  • hookSpecificOutput.additionalContext:要追加到提示词的额外上下文(可选)

注意:由于 UserPromptSubmitOutput 继承了 HookOutput,因此所有标准字段都可用,但只有 hookSpecificOutput 中的 additionalContext 是专门为此事件定义的。

输出示例

{ "decision": "allow", "reason": "Prompt reviewed and approved", "hookSpecificOutput": { "additionalContext": "Remember to follow company coding standards." } }

SessionStart

用途:在新会话开始时执行,用于执行初始化任务。

事件特定字段

{ "permission_mode": "default | plan | auto_edit | yolo", "source": "startup | resume | clear | compact", "model": "the model being used", "agent_type": "the type of agent if applicable (optional)" }

输出选项

  • hookSpecificOutput.additionalContext:在会话中可用的上下文
  • 标准 Hook 输出字段

输出示例

{ "hookSpecificOutput": { "additionalContext": "Session started with security policies enabled." } }

SessionEnd

用途:在会话结束时执行,用于执行清理任务。

事件特定字段

{ "reason": "clear | logout | prompt_input_exit | bypass_permissions_disabled | other" }

输出选项

  • 标准 Hook 输出字段(通常不用于阻塞)

Stop

用途:在 Qwen 结束其响应之前执行,用于提供最终反馈或总结。

事件特定字段

{ "stop_hook_active": "boolean indicating if stop hook is active", "last_assistant_message": "the last message from the assistant", "context_usage": "ratio of context window used (may exceed 1 when tokens exceed window; optional)", "context_limit": "context window size in tokens (optional)", "input_tokens": "prompt token count (may include output tokens depending on provider; optional)" }

context_usagecontext_limitinput_tokens 字段允许 Hook 脚本观察上下文使用情况并实现自定义的 compact 策略——例如,当使用量超过自定义阈值时,打印运行 /compact 的提醒脚本。

输出选项

  • decision:“allow”、“deny”、“block” 或 “ask”
  • reason:决策的人类可读解释
  • stopReason:包含在停止响应中的反馈
  • continue:设置为 false 以停止执行
  • hookSpecificOutput.additionalContext:额外的上下文信息

注意:由于 StopOutput 继承了 HookOutput,因此所有标准字段都可用,但 stopReason 字段与此事件特别相关。

输出示例

{ "decision": "block", "reason": "Must be provided when Qwen Code is blocked from stopping" }

StopFailure

用途:当回合因 API 错误而结束(而不是 Stop)时执行。这是一个即发即弃(fire-and-forget) 事件——Hook 输出和退出码会被忽略。

事件特定字段

{ "error": "rate_limit | authentication_failed | billing_error | invalid_request | server_error | max_output_tokens | unknown", "error_details": "detailed error message (optional)", "last_assistant_message": "the last message from the assistant before the error (optional)" }

Matcher:匹配 error 字段。例如,"matcher": "rate_limit" 仅在触发速率限制错误时执行。

输出选项

  • None - StopFailure 采用“触发即忘”模式。所有 hook 输出和退出码均被忽略。

退出码处理

退出码行为
任意已忽略(触发即忘)

配置示例

{ "hooks": { "StopFailure": [ { "matcher": "rate_limit", "hooks": [ { "type": "command", "command": "/path/to/rate-limit-alert.sh", "name": "rate-limit-alerter" } ] } ] } }

使用场景

  • 速率限制监控与告警
  • 身份验证失败日志记录
  • 计费错误通知
  • 错误统计收集

SubagentStart

用途:在启动子代理(如 Task 工具)时执行,用于设置上下文或权限。

事件特定字段

{ "permission_mode": "default | plan | auto_edit | yolo", "agent_id": "identifier for the subagent", "agent_type": "type of agent (Bash, Explorer, Plan, Custom, etc.)" }

输出选项

  • hookSpecificOutput.additionalContext:子代理的初始上下文
  • 标准 hook 输出字段

输出示例

{ "hookSpecificOutput": { "additionalContext": "Subagent initialized with restricted permissions." } }

SubagentStop

用途:在子代理完成时执行,用于执行收尾任务。

事件特定字段

{ "permission_mode": "default | plan | auto_edit | yolo", "stop_hook_active": "boolean indicating if stop hook is active", "agent_id": "identifier for the subagent", "agent_type": "type of agent", "agent_transcript_path": "path to the subagent's transcript", "last_assistant_message": "the last message from the subagent" }

输出选项

  • decision:“allow”、“deny”、“block” 或 “ask”
  • reason:决策的人类可读解释

输出示例

{ "decision": "block", "reason": "Must be provided when Qwen Code is blocked from stopping" }

PreCompact

用途:在对话压缩(compaction)之前执行,用于准备或记录压缩操作。

事件特定字段

{ "trigger": "manual | auto", "custom_instructions": "custom instructions currently set" }

输出选项

  • hookSpecificOutput.additionalContext:压缩前要包含的上下文
  • 标准 hook 输出字段

输出示例

{ "hookSpecificOutput": { "additionalContext": "Compacting conversation to maintain optimal context window." } }

PostCompact

用途:在对话压缩完成后执行,用于归档摘要或跟踪使用情况。

事件特定字段

{ "trigger": "manual | auto", "compact_summary": "the summary generated by the compaction process" }

Matcher:匹配 trigger 字段。例如,"matcher": "manual" 仅在通过 /compact 命令进行手动压缩时触发。

输出选项

  • hookSpecificOutput.additionalContext:附加上下文(仅用于日志记录)
  • 标准 hook 输出字段(仅用于日志记录)

注意:PostCompact 不在官方决策模式支持的事件列表中。decision 字段和其他控制字段不会产生任何控制效果——它们仅用于日志记录目的。

退出码处理

退出码行为
0成功 - 在详细模式下向用户显示 stdout
其他非阻塞错误 - 在详细模式下向用户显示 stderr

配置示例

{ "hooks": { "PostCompact": [ { "matcher": "manual", "hooks": [ { "type": "command", "command": "/path/to/save-compact-summary.sh", "name": "save-summary" } ] } ] } }

使用场景

  • 将摘要归档到文件或数据库
  • 使用统计跟踪
  • 上下文变更监控
  • 压缩操作的审计日志

Notification

用途:在发送通知时执行,用于自定义或拦截通知。

事件特定字段

{ "message": "notification message content", "title": "notification title (optional)", "notification_type": "permission_prompt | idle_prompt | auth_success" }

注意elicitation_dialog 类型已定义但尚未实现。

输出选项

  • hookSpecificOutput.additionalContext:要包含的附加信息
  • 标准 hook 输出字段

输出示例

{ "hookSpecificOutput": { "additionalContext": "Notification processed by monitoring system." } }

PermissionRequest

用途:在显示权限对话框时执行,用于自动化决策或更新权限。

事件特定字段

{ "permission_mode": "default | plan | auto_edit | yolo", "tool_name": "name of the tool requesting permission", "tool_input": "object containing the tool's input parameters", "permission_suggestions": "array of suggested permissions (optional)" }

输出选项

  • hookSpecificOutput.decision:包含权限决策详情的结构化对象:
    • behavior:“allow” 或 “deny”
    • updatedInput:修改后的工具输入(可选)
    • updatedPermissions:修改后的权限(可选)
    • message:显示给用户的消息(可选)
    • interrupt:是否中断工作流(可选)

输出示例

{ "hookSpecificOutput": { "decision": { "behavior": "allow", "message": "Permission granted based on security policy", "interrupt": false } } }

TodoCreated

用途:在通过 todo_write 工具创建新的 todo 项时执行。允许对 todo 创建进行验证、记录日志或阻止。

Todo hook 分两个阶段运行:

  • validation:在持久化之前运行。仅在此阶段进行验证;返回 blockdeny 可阻止写入。
  • postWrite:在持久化之后运行。用于日志记录或同步等副作用;此阶段会忽略 blockdeny

事件特定字段

{ "todo_id": "unique identifier for the todo item", "todo_content": "content/description of the todo item", "todo_status": "pending | in_progress | completed", "all_todos": "array of all todo items in the current list", "phase": "validation | postWrite" }

输出选项

  • decision:“allow”、“block” 或 “deny”
  • reason:决策的人类可读解释(阻止时必填)

阻止行为

validation 阶段,当 decisionblockdeny(退出码 2)时,会阻止创建 todo。todo 列表保持不变,并将原因作为反馈提供给模型。

postWrite 阶段,todo 已被持久化。Hook 仍可返回输出,但 block / deny 不会撤销写入,也不应用于验证。

输出示例(允许)

{ "decision": "allow", "reason": "Todo content validated successfully" }

输出示例(阻止)

{ "decision": "block", "reason": "Todo content too short. Minimum 5 characters required." }

Hook 脚本示例

#!/bin/bash # ~/.qwen/hooks/todo-validator.sh # Validates todo content before creation INPUT=$(cat) CONTENT=$(echo "$INPUT" | jq -r '.todo_content') # Check minimum length if [ ${#CONTENT} -lt 5 ]; then echo '{"decision": "block", "reason": "Todo content must be at least 5 characters"}' exit 2 fi # Block test-related todos if [[ "$CONTENT" =~ "test" ]]; then echo '{"decision": "block", "reason": "Test todos are not allowed in production"}' exit 2 fi echo '{"decision": "allow"}' exit 0

配置示例

{ "hooks": { "TodoCreated": [ { "hooks": [ { "type": "command", "command": "$HOME/.qwen/hooks/todo-validator.sh", "name": "todo-validator", "timeout": 5000 } ] } ] } }

TodoCompleted

用途:在将 todo 项标记为已完成时执行。允许对 todo 完成进行验证、记录日志或阻止。

Todo hook 分两个阶段运行:

  • validation:在持久化之前运行。仅在此阶段进行验证;返回 blockdeny 可阻止写入。
  • postWrite:在持久化之后运行。用于日志记录或同步等副作用;此阶段会忽略 blockdeny

事件特定字段

{ "todo_id": "unique identifier for the todo item", "todo_content": "content/description of the todo item", "previous_status": "pending | in_progress (status before completion)", "all_todos": "array of all todo items in the current list", "phase": "validation | postWrite" }

输出选项

  • decision:“allow”、“block” 或 “deny”
  • reason:决策的人类可读解释(阻止时必填)

阻止行为

validation 阶段,当 decisionblockdeny(退出码 2)时,会阻止完成 todo。todo 项保持其先前状态,并将原因作为反馈提供给模型。

postWrite 阶段,todo 已被持久化。Hook 仍可返回输出,但 block / deny 不会撤销写入,也不应用于验证。

输出示例(允许)

{ "decision": "allow", "reason": "Todo completion approved" }

输出示例(阻止)

{ "decision": "block", "reason": "Cannot complete this todo until dependent tasks are finished." }

Hook 脚本示例

#!/bin/bash # ~/.qwen/hooks/todo-completion-validator.sh # Validates todo completion conditions INPUT=$(cat) TODO_ID=$(echo "$INPUT" | jq -r '.todo_id') ALL_TODOS=$(echo "$INPUT" | jq -r '.all_todos') # Check if there are incomplete dependent todos (example logic) INCOMPLETE_COUNT=$(echo "$ALL_TODOS" | jq '[.[] | select(.status != "completed")] | length') if [ "$INCOMPLETE_COUNT" -gt 5 ]; then echo '{"decision": "block", "reason": "Too many incomplete todos. Complete other tasks first."}' exit 2 fi echo '{"decision": "allow"}' exit 0

配置示例

{ "hooks": { "TodoCompleted": [ { "hooks": [ { "type": "command", "command": "$HOME/.qwen/hooks/todo-completion-validator.sh", "name": "completion-validator", "timeout": 5000 } ] } ] } }

使用场景

  • 日志记录:跟踪 todo 的创建和完成情况以进行审计或分析
  • 验证:强制执行内容质量标准(最小长度、必需关键字)
  • 工作流控制:在满足先决条件之前阻止完成
  • 集成:将 todo 与外部任务管理系统(Jira、Trello 等)同步

Hook 配置

Hook 在 Qwen Code 设置中进行配置,通常位于 .qwen/settings.json 或用户配置文件中:

{ "hooks": { "PreToolUse": [ { "matcher": "^Bash$", "sequential": false, "hooks": [ { "type": "command", "command": "/path/to/security-check.sh", "name": "security-check", "description": "Run security checks before tool execution", "timeout": 30000 } ] } ], "SessionStart": [ { "hooks": [ { "type": "command", "command": "echo 'Session started'", "name": "session-init" } ] } ] } }

Hook 执行

并行与顺序执行

  • 默认情况下,hook 并行执行以提升性能
  • 在 hook 定义中使用 sequential: true 以强制执行顺序执行
  • 顺序执行的 hook 可以修改链路中后续 hook 的输入

异步 Hook

只有 command 类型支持异步执行。设置 "async": true 会在后台运行 hook,不会阻塞主流程。

特性:

  • 无法返回决策控制(操作已发生)
  • 结果会在下一轮对话中通过 systemMessageadditionalContext 注入
  • 适用于审计、日志记录、后台测试等场景

示例:

{ "hooks": { "PostToolUse": [ { "matcher": "WriteFile|Edit", "hooks": [ { "type": "command", "command": "$QWEN_PROJECT_DIR/.qwen/hooks/run-tests-async.sh", "async": true, "timeout": 300000 } ] } ] } }
#!/bin/bash INPUT=$(cat) FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then exit 0; fi RESULT=$(npm test 2>&1) if [ $? -eq 0 ]; then echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}" else echo "{\"systemMessage\": \"Tests failed: $RESULT\"}" fi

安全模型

  • Hook 在用户环境中以用户权限运行
  • 项目级 hook 需要受信任的文件夹状态
  • 超时机制可防止 hook 挂起(默认:60 秒)

最佳实践

示例 1:安全验证 Hook

一个 PreToolUse hook,用于记录日志并可能拦截危险命令:

security_check.sh

#!/bin/bash # Read input from stdin INPUT=$(cat) # Parse the input to extract tool info TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name') TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input') # Check for potentially dangerous operations if echo "$TOOL_INPUT" | grep -qiE "(rm.*-rf|mv.*\/|chmod.*777)"; then echo '{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Security policy blocks dangerous command" } }' exit 2 # Blocking error fi # Log the operation echo "INFO: Tool $TOOL_NAME executed safely at $(date)" >> /var/log/qwen-security.log # Allow with additional context echo '{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "Security check passed", "additionalContext": "Command approved by security policy" } }' exit 0

.qwen/settings.json 中配置:

{ "hooks": { "PreToolUse": [ { "hooks": [ { "type": "command", "command": "${SECURITY_CHECK_SCRIPT}", "name": "security-checker", "description": "Security validation for bash commands", "timeout": 10000 } ] } ] } }

示例 2:HTTP 审计 Hook

一个 PostToolUse HTTP hook,用于将所有工具执行记录发送至远程审计服务:

{ "hooks": { "PostToolUse": [ { "matcher": "*", "hooks": [ { "type": "http", "url": "https://audit.example.com/api/tool-execution", "headers": { "Authorization": "Bearer ${AUDIT_API_TOKEN}", "Content-Type": "application/json" }, "allowedEnvVars": ["AUDIT_API_TOKEN"], "timeout": 10, "name": "audit-logger" } ] } ] } }

示例 3:用户提示词验证 Hook

一个 UserPromptSubmit hook,用于校验用户提示词是否包含敏感信息,并为过长的提示词提供上下文:

prompt_validator.py

import json import sys import re # Load input from stdin try: input_data = json.load(sys.stdin) except json.JSONDecodeError as e: print(f"Error: Invalid JSON input: {e}", file=sys.stderr) exit(1) user_prompt = input_data.get("prompt", "") # Sensitive words list sensitive_words = ["password", "secret", "token", "api_key"] # Check for sensitive information for word in sensitive_words: if re.search(rf"\b{word}\b", user_prompt.lower()): # Block prompts containing sensitive information output = { "decision": "block", "reason": f"Prompt contains sensitive information '{word}'. Please remove sensitive content and resubmit.", "hookSpecificOutput": { "hookEventName": "UserPromptSubmit" } } print(json.dumps(output)) exit(0) # Check prompt length and add warning context if too long if len(user_prompt) > 1000: output = { "hookSpecificOutput": { "hookEventName": "UserPromptSubmit", "additionalContext": "Note: User submitted a long prompt. Please read carefully and ensure all requirements are understood." } } print(json.dumps(output)) exit(0) # No processing needed for normal cases exit(0)

故障排除

  • 检查应用日志以获取 hook 执行详情
  • 验证 hook 脚本的权限和可执行性
  • 确保 hook 输出中的 JSON 格式正确
  • 使用特定的 matcher 模式以避免意外的 hook 执行
  • 使用 --debug 模式查看详细的 hook 匹配和执行信息
  • 临时禁用所有 hook:在设置中添加 "disableAllHooks": true
Last updated on