学习目标:掌握Claude Code Hooks事件系统的完整架构,学会用七类生命周期事件构建自动化管道,将
-p非交互模式与Hooks结合打造无人值守的CI门禁系统。适用人群:已熟悉Claude Code基本操作、希望将AI编程能力集成到团队自动化流水线的开发者。
| 条件 | 说明 |
|---|---|
| Claude Code ≥ 1.0 | claude --version 确认 |
| Node.js ≥ 18 | Hooks脚本运行环境 |
| jq | JSON处理(brew install jq 或 apt install jq) |
| 一个Git仓库 | 本文示例基于真实项目结构 |
| 基础Shell知识 | bash、管道、退出码概念 |
核心概念速查:Claude Code有两种运行模式——交互模式(Interactive)和非交互/Headless模式(通过 -p / --print 触发)。Hooks是贯穿两种模式的事件拦截层。
Claude Code的Hooks系统是一个面向切面编程(AOP) 的事件总线。每个Hook在Agent循环的特定阶段触发,接收JSON输入,通过退出码和stdout返回决策。
sequenceDiagram
participant U as 用户/脚本
participant CC as Claude Code Agent Loop
participant H as Hooks事件总线
participant T as 工具(Tool)
Note over CC: SessionStart → 触发一次
U->>CC: 发送Prompt
CC->>H: UserPromptSubmit事件
H-->>CC: 放行(exit 0) 或 注入上下文
loop Agent Loop (每轮迭代)
CC->>H: PreToolUse事件
H-->>CC: 允许(exit 0) / 阻止(exit 2) / 询问用户(exit 3)
alt 工具被允许
CC->>T: 执行工具(Bash/Edit/Write...)
T-->>CC: 工具结果
CC->>H: PostToolUse事件
H-->>CC: 日志/后处理
end
end
Note over CC: Stop事件 → Agent完成
CC->>H: Notification事件(可选)
H-->>U: 通知(Slack/邮件/桌面)
CC-->>U: 最终回复
七类核心生命周期事件(截至2026年5月):
| 事件类型 | 触发时机 | 核心用途 | 退出码行为 |
|---|---|---|---|
| SessionStart | 会话初始化时触发一次 | 加载上下文、设置环境变量 | 0=继续,2=终止 |
| UserPromptSubmit | 用户提交Prompt后、Agent处理前 | 注入系统提示、预处理指令 | 0=继续,2=阻止提交 |
| PreToolUse | 工具执行前 | 安全审查、权限控制、输入校验 | 0=允许,2=阻止,3=询问用户 |
| PostToolUse | 工具执行后 | 自动格式化、审计日志、触发后处理 | 0=继续(可修改结果) |
| Stop | Agent完成任务后 | 质量检查、结果验证、汇总报告 | 0=完成,2=继续工作 |
| SubagentStop | 子Agent完成时 | 收集子Agent结果、协调多Agent | 0=完成,2=继续 |
| Notification | 需要通知用户时 | 桌面通知、Slack/邮件推送 | 0=已处理 |
Hooks在 settings.json 中声明式配置,支持三层优先级:
~/.claude/settings.json ← 全局(所有项目)
.claude/settings.json ← 项目级(推荐)
.claude/settings.local.json ← 本地覆盖(不入Git)
基本配置结构:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/pre-bash.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write $CLAUDE_FILE_PATH 2>/dev/null"
}
]
}
]
}
}
Matcher语法:使用正则表达式匹配工具名称。Bash 匹配Bash工具调用,Write|Edit 匹配文件写入和编辑操作,.* 匹配所有工具。
-p(--print)标志将Claude Code转换为标准命令行工具——接收输入,输出结果,然后退出。
# 最基本用法
claude -p "解释这段代码的作用:$(cat main.go)"
# 管道输入
echo "为这个函数写单元测试" | claude -p
# 指定输出格式
claude -p "列出所有TODO注释" --output-format json
# 流式输出(逐token输出,适合实时展示)
claude -p "重构这个模块" --output-format stream-json
# 纯文本输出(默认)
claude -p "生成README" --output-format text
这是自动化管道的关键——让Claude返回可预测的JSON结构:
# 定义期望的输出Schema
cat > /tmp/review-schema.json << 'EOF'
{
"type": "object",
"properties": {
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"file": { "type": "string" },
"line": { "type": "number" },
"severity": { "enum": ["error", "warning", "info"] },
"message": { "type": "string" }
},
"required": ["file", "severity", "message"]
}
},
"summary": { "type": "string" }
},
"required": ["issues", "summary"]
}
EOF
# 使用Schema约束输出
claude -p "审查当前目录的代码质量" \
--output-format json \
--json-schema "$(cat /tmp/review-schema.json)"
预期输出:
{
"issues": [
{
"file": "src/auth.ts",
"line": 42,
"severity": "error",
"message": "SQL注入风险:直接拼接用户输入"
},
{
"file": "src/utils.ts",
"line": 15,
"severity": "warning",
"message": "未处理的Promise rejection"
}
],
"summary": "发现1个严重问题和1个警告,建议优先修复auth.ts中的SQL注入"
}
-p 模式支持通过 --resume 恢复之前的会话,实现多轮自动化:
# 第一轮:分析代码
SESSION_ID=$(claude -p "分析src/目录的架构问题" --output-format json | jq -r '.session_id')
# 第二轮:基于分析结果生成修复方案(同一会话上下文)
claude -p "根据刚才的分析,生成修复计划" --resume "$SESSION_ID"
# 第三轮:执行修复
claude -p "按照修复计划,开始修改代码" --resume "$SESSION_ID" \
--permission-mode acceptEdits
非交互模式下的权限控制是自动化的安全基石:
| 模式 | 标志 | 行为 | 适用场景 |
|---|---|---|---|
default |
(默认) | 每次操作需要确认 | ❌ 不适合自动化 |
plan |
--permission-mode plan |
只规划不执行 | 代码审查、分析 |
acceptEdits |
--permission-mode acceptEdits |
自动接受文件编辑 | 代码生成、重构 |
bypassPermissions |
--dangerously-skip-permissions |
跳过所有权限检查 | ⚠️ 仅CI沙箱环境 |
# 安全的自动化模式:只允许特定工具
claude -p "运行测试并修复失败用例" \
--permission-mode acceptEdits \
--allowedTools "Bash(npm test:*),Edit,Read,Write"
每个Hook通过 stdin 接收JSON,通过退出码和 stdout 返回决策。这是理解Hooks的核心:
PreToolUse 输入示例:
{
"event": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules && npm install",
"description": "清理并重新安装依赖"
},
"session_id": "abc123",
"conversation_id": "conv-456"
}
Hook脚本模板:
#!/bin/bash
# .claude/hooks/pre-bash-guard.sh
# 从stdin读取JSON输入
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# 规则1:阻止危险命令
DANGEROUS_PATTERNS=('rm -rf /' 'drop table' 'dd if=' ':(){ :|:& };:' 'mkfs.')
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi "$pattern"; then
# 退出码2 = 阻止操作,stdout作为拒绝理由
echo "BLOCKED: 检测到危险命令模式: $pattern"
exit 2
fi
done
# 规则2:阻止访问敏感文件
if echo "$COMMAND" | grep -qE '\.(env|pem|key|p12)\b'; then
echo "BLOCKED: 禁止访问密钥/凭证文件"
exit 2
fi
# 退出码0 = 允许操作
exit 0
PostToolUse是自动化的主力——在工具执行后自动触发后处理:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/post-format.sh"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/post-bash-audit.sh"
}
]
}
]
}
}
自动格式化脚本:
#!/bin/bash
# .claude/hooks/post-format.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE_PATH" ] && exit 0
[ ! -f "$FILE_PATH" ] && exit 0
# 根据文件类型选择格式化工具
case "$FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx)
npx prettier --write "$FILE_PATH" 2>/dev/null
npx eslint --fix "$FILE_PATH" 2>/dev/null
;;
*.py)
python -m black "$FILE_PATH" 2>/dev/null
python -m isort "$FILE_PATH" 2>/dev/null
;;
*.go)
gofmt -w "$FILE_PATH" 2>/dev/null
;;
*.rs)
rustfmt "$FILE_PATH" 2>/dev/null
;;
esac
exit 0
SessionStart事件可以返回额外上下文注入到Agent的Prompt中,实现动态项目配置:
#!/bin/bash
# .claude/hooks/session-context.sh
# 读取Git分支名
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
# 读取最近的变更文件
CHANGED_FILES=$(git diff --name-only HEAD~1 2>/dev/null | head -20 | tr '\n' ',')
# 读取package.json中的项目名
PROJECT=$(jq -r '.name // "unknown"' package.json 2>/dev/null)
# 输出JSON格式的额外上下文(stdout会被注入到Prompt中)
cat << EOF
{"extra_context": "当前项目: $PROJECT | 分支: $BRANCH | 最近变更: $CHANGED_FILES | 请在回复中考虑这些上下文信息。"}
EOF
exit 0
Stop事件在Agent完成任务时触发,可以作为最终的质量检查点:
#!/bin/bash
# .claude/hooks/stop-quality-gate.sh
INPUT=$(cat)
# 检查是否有未提交的lint错误
LINT_ERRORS=$(npx eslint src/ --format json 2>/dev/null | jq '[.[] | select(.errorCount > 0)] | length')
if [ "$LINT_ERRORS" -gt 0 ]; then
# 退出码2 = 让Agent继续工作(修复lint错误)
echo "检测到 $LINT_ERRORS 个Lint错误,请修复后重新检查。"
exit 2
fi
# 检查测试是否通过
TEST_RESULT=$(npm test 2>&1)
if echo "$TEST_RESULT" | grep -q "FAIL"; then
echo "测试未通过,请修复失败的测试用例。"
exit 2
fi
echo "质量检查通过"
exit 0
#!/bin/bash
# .claude/hooks/notify.sh
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "任务完成"')
TITLE=$(echo "$INPUT" | jq -r '.title // "Claude Code"')
# macOS桌面通知
if [[ "$OSTYPE" == "darwin"* ]]; then
osascript -e "display notification \"$MESSAGE\" with title \"$TITLE\""
fi
# Linux桌面通知
if [[ "$OSTYPE" == "linux"* ]]; then
notify-send "$TITLE" "$MESSAGE" 2>/dev/null
fi
# Slack通知(如果配置了Webhook)
if [ -n "$SLACK_WEBHOOK_URL" ]; then
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\": \"[$TITLE] $MESSAGE\"}" > /dev/null 2>&1
fi
exit 0
下面我们将构建一个完整的CI门禁系统,结合 -p 模式和Hooks,实现:代码提交 → 自动审查 → 自动修复 → 质量门禁 → 通知。
flowchart TB
subgraph 触发层
A[Git Push / PR创建] --> B[GitHub Actions触发]
C[Cron定时任务] --> D[Shell脚本调度]
E[Webhook事件] --> F[事件处理器]
end
subgraph 管道层
B --> G[claude -p 分析]
D --> G
F --> G
G --> H{JSON输出解析}
H -->|有严重问题| I[claude -p 修复 --resume]
H -->|无问题| J[质量检查通过]
I --> K[Hooks Stop门禁]
K -->|exit 2| I
K -->|exit 0| J
end
subgraph 通知层
J --> L[Notification Hook]
L --> M[Slack/邮件/PR评论]
end
style G fill:#4A90D9,color:#fff
style K fill:#E67E22,color:#fff
style J fill:#27AE60,color:#fff
project/
├── .claude/
│ ├── settings.json # Hooks配置
│ ├── CLAUDE.md # 项目指令
│ └── hooks/
│ ├── pre-bash-guard.sh # Bash命令守卫
│ ├── post-format.sh # 自动格式化
│ ├── post-bash-audit.sh # 命令审计日志
│ ├── stop-gate.sh # 完成质量门禁
│ ├── session-context.sh # 动态上下文注入
│ └── notify.sh # 多通道通知
├── .github/
│ └── workflows/
│ └── claude-review.yml # CI管道配置
├── scripts/
│ ├── batch-review.sh # 批量审查脚本
│ └── auto-fix.sh # 自动修复脚本
└── src/
└── ...
// .claude/settings.json
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/session-context.sh"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/pre-bash-guard.sh"
}
]
},
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/pre-write-protect.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/post-format.sh"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/post-bash-audit.sh"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/stop-gate.sh"
}
]
}
],
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/notify.sh"
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/pre-write-protect.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# 保护关键配置文件
PROTECTED_FILES=(
"package-lock.json"
"yarn.lock"
"pnpm-lock.yaml"
".env.production"
"terraform.tfstate"
"docker-compose.prod.yml"
)
for protected in "${PROTECTED_FILES[@]}"; do
if [[ "$FILE_PATH" == *"$protected"* ]]; then
echo "BLOCKED: 文件 $FILE_PATH 受保护,禁止自动修改"
exit 2
fi
done
# 保护生产分支上的敏感目录
BRANCH=$(git branch --show-current 2>/dev/null)
if [[ "$BRANCH" == "main" || "$BRANCH" == "master" || "$BRANCH" == "release"* ]]; then
if [[ "$FILE_PATH" == src/core/* || "$FILE_PATH" == src/migrations/* ]]; then
echo "BLOCKED: 分支 $BRANCH 上禁止修改 $FILE_PATH,请通过PR流程"
exit 2
fi
fi
exit 0
#!/bin/bash
# .claude/hooks/post-bash-audit.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
RESULT=$(echo "$INPUT" | jq -r '.tool_result // empty')
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
SESSION=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
LOG_DIR=".claude/audit"
mkdir -p "$LOG_DIR"
# 写入审计日志
echo "$INPUT" | jq --arg ts "$TIMESTAMP" --arg sid "$SESSION" \
'. + {audit_timestamp: $ts, audit_session: $sid}' \
>> "$LOG_DIR/commands-$(date +%Y%m%d).jsonl"
# 高风险操作实时告警
if echo "$COMMAND" | grep -qE '(curl|wget|ssh|scp|rsync|nc )'; then
echo "[AUDIT] 网络操作: $COMMAND" >> "$LOG_DIR/security-alerts.log"
fi
exit 0
# .github/workflows/claude-review.yml
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
push:
branches: [main, develop]
jobs:
ai-review:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Claude Code
run: |
npm install -g @anthropic-ai/claude-code
echo "Installed claude $(claude --version)"
- name: Analyze Changes
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
id: analyze
run: |
# 获取变更文件列表
if [ "${{ github.event_name }}" = "pull_request" ]; then
DIFF_CMD="git diff origin/main...HEAD"
else
DIFF_CMD="git diff HEAD~1 HEAD"
fi
# 运行Claude分析
RESULT=$(claude -p "你是一个代码审查专家。分析以下Git Diff,输出JSON格式的审查结果:
{
\"issues\": [{\"file\": \"\", \"line\": 0, \"severity\": \"error|warning|info\", \"message\": \"\"}],
\"score\": 0-100,
\"summary\": \"一句话总结\"
}
Diff内容:
$($DIFF_CMD)" \
--output-format json \
--permission-mode plan \
--bare \
--max-turns 3)
echo "result<<EOF" >> $GITHUB_OUTPUT
echo "$RESULT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Post Review Comment
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RESULT='${{ steps.analyze.outputs.result }}'
SCORE=$(echo "$RESULT" | jq -r '.score // "N/A"')
SUMMARY=$(echo "$RESULT" | jq -r '.summary // "No summary"')
ISSUES=$(echo "$RESULT" | jq -r '.issues | length')
# 格式化评论
cat > /tmp/comment.md << COMMENT
## Claude Code Review
**评分**: ${SCORE}/100
**问题数**: ${ISSUES}
${SUMMARY}
$(echo "$RESULT" | jq -r '.issues[] | "### \(.severity | ascii_upcase): \(.file):\(.line // \"N/A\")\n\(.message)\n"' 2>/dev/null)
COMMENT
gh pr comment ${{ github.event.pull_request.number }} \
--body-file /tmp/comment.md
- name: Quality Gate
run: |
RESULT='${{ steps.analyze.outputs.result }}'
ERRORS=$(echo "$RESULT" | jq '[.issues[] | select(.severity == "error")] | length')
if [ "$ERRORS" -gt 0 ]; then
echo "::error::发现 $ERRORS 个严重问题"
exit 1
fi
echo "✅ 代码质量检查通过"
对多个目录或多个仓库进行批量自动化审查:
#!/bin/bash
# scripts/batch-review.sh
# 用法: ./batch-review.sh [目录或文件列表]
set -euo pipefail
TARGETS=("${@:-src/}")
REPORT_DIR=".claude/reports/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$REPORT_DIR"
echo "=== Claude Code 批量审查 ==="
echo "目标: ${TARGETS[*]}"
echo "报告目录: $REPORT_DIR"
echo ""
# 定义审查Schema
REVIEW_SCHEMA='{
"type": "object",
"properties": {
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"file": {"type": "string"},
"line": {"type": "number"},
"severity": {"enum": ["error","warning","info"]},
"category": {"enum": ["security","performance","maintainability","style"]},
"message": {"type": "string"}
}
}
},
"score": {"type": "number"},
"summary": {"type": "string"}
}
}'
TOTAL_ISSUES=0
TOTAL_FILES=0
# 函数:审查单个目标
review_target() {
local target="$1"
local output_file="$REPORT_DIR/$(echo "$target" | tr '/' '_').json"
echo "审查: $target"
# 使用-p模式进行非交互审查
claude -p "审查 $target 中的代码质量问题,关注安全性、性能和可维护性。" \
--output-format json \
--json-schema "$REVIEW_SCHEMA" \
--permission-mode plan \
--bare \
--max-turns 2 \
> "$output_file" 2>/dev/null
# 解析结果
local issues=$(jq '[.issues // []] | flatten | length' "$output_file" 2>/dev/null || echo 0)
local score=$(jq '.score // 0' "$output_file" 2>/dev/null || echo 0)
TOTAL_ISSUES=$((TOTAL_ISSUES + issues))
TOTAL_FILES=$((TOTAL_FILES + 1))
echo " 发现 $issues 个问题,评分: $score/100"
}
# 并行审查(最多4个并发)
export -f review_target
export REPORT_DIR REVIEW_SCHEMA TOTAL_ISSUES TOTAL_FILES
for target in "${TARGETS[@]}"; do
# 如果目标是目录,按子目录拆分并行处理
if [ -d "$target" ]; then
find "$target" -maxdepth 1 -type d | tail -n +2 | \
xargs -P 4 -I {} bash -c 'review_target "$@"' _ {}
else
review_target "$target"
fi
done
# 生成汇总报告
echo ""
echo "=== 审查汇总 ==="
echo "审查目标数: ${#TARGETS[@]}"
echo "发现问题: $TOTAL_ISSUES"
# 合并所有报告
jq -s '{
total_issues: [.[].issues // [] | flatten] | add | length,
avg_score: ([.[].score // 0] | add / length),
by_severity: {
errors: [.[].issues // [] | flatten | .[] | select(.severity == "error")] | length,
warnings: [.[].issues // [] | flatten | .[] | select(.severity == "warning")] | length,
info: [.[].issues // [] | flatten | .[] | select(.severity == "info")] | length
},
reports: .
}' "$REPORT_DIR"/*.json > "$REPORT_DIR/summary.json" 2>/dev/null
echo "详细报告: $REPORT_DIR/summary.json"
# 如果有严重错误,退出码非0
ERRORS=$(jq '.by_severity.errors // 0' "$REPORT_DIR/summary.json" 2>/dev/null || echo 0)
if [ "$ERRORS" -gt 0 ]; then
echo "⚠️ 发现 $ERRORS 个严重问题,请修复后重新审查"
exit 1
fi
echo "✅ 所有检查通过"
审查发现问题后,自动触发修复流程:
#!/bin/bash
# scripts/auto-fix.sh
# 用法: ./auto-fix.sh <审查报告JSON>
set -euo pipefail
REPORT_FILE="${1:?用法: auto-fix.sh <report.json>}"
MAX_FIX_ROUNDS=3
[ ! -f "$REPORT_FILE" ] && { echo "报告文件不存在: $REPORT_FILE"; exit 1; }
# 提取所有error级别的问题
ERRORS=$(jq -c '[.issues // [] | flatten | .[] | select(.severity == "error")]' "$REPORT_FILE")
if [ "$ERRORS" == "[]" ]; then
echo "没有严重问题需要修复"
exit 0
fi
echo "发现需要自动修复的问题:"
echo "$ERRORS" | jq -r '.[] | " [\(.severity)] \(.file):\(.line // "N/A") - \(.message)"'
# 构造修复Prompt
FIX_PROMPT="请修复以下代码问题,每个问题修复后运行相关测试确认修复有效:
$(echo "$ERRORS" | jq -r '.[] | "- \(.file):\(.line // "N/A"): \(.message)"')"
# 第一轮:初始会话
SESSION=$(claude -p "$FIX_PROMPT" \
--permission-mode acceptEdits \
--output-format json \
--bare \
--max-turns 10 | jq -r '.session_id // empty')
if [ -z "$SESSION" ]; then
echo "修复失败:无法创建会话"
exit 1
fi
# 循环检查修复结果
for round in $(seq 2 $MAX_FIX_ROUNDS); do
echo "--- 修复轮次 $round/$MAX_FIX_ROUNDS ---"
# 重新运行审查
NEW_REPORT=".claude/reports/fix-round-$round.json"
claude -p "重新检查之前的问题是否已修复,是否引入了新问题" \
--resume "$SESSION" \
--output-format json \
--json-schema "$(jq -c '.' /tmp/review-schema.json 2>/dev/null || echo '{}')" \
--permission-mode plan \
--bare \
--max-turns 3 \
> "$NEW_REPORT"
# 检查剩余错误
REMAINING=$(jq '[.issues // [] | flatten | .[] | select(.severity == "error")] | length' "$NEW_REPORT")
if [ "$REMAINING" -eq 0 ]; then
echo "✅ 所有问题已修复"
exit 0
fi
echo "仍有 $REMAINING 个问题,继续修复..."
done
echo "⚠️ 达到最大修复轮次 ($MAX_FIX_ROUNDS),请手动检查"
exit 1
通过将多个Hook串联,构建复杂的事件处理链:
stateDiagram-v2
[*] --> SessionStart: 会话开始
SessionStart --> Idle: 加载项目上下文
Idle --> UserPromptSubmit: 用户输入
UserPromptSubmit --> PreToolUse: Agent决定使用工具
state PreToolUse {
[*] --> 检查命令安全性
检查命令安全性 --> 检查文件保护: 通过
检查命令安全性 --> [*]: 阻止(exit 2)
检查文件保护 --> [*]: 通过(exit 0)
检查文件保护 --> [*]: 阻止(exit 2)
}
PreToolUse --> ToolExecution: 允许
PreToolUse --> Idle: 阻止
ToolExecution --> PostToolUse: 执行完成
state PostToolUse {
[*] --> 自动格式化
自动格式化 --> 审计日志: 完成
审计日志 --> [*]: 记录完成
}
PostToolUse --> Idle: 继续Agent Loop
PostToolUse --> Stop: 任务完成
state Stop {
[*] --> 运行Lint
运行Lint --> 运行测试: 通过
运行Lint --> [*]: 失败→继续修复
运行测试 --> [*]: 通过(exit 0)
运行测试 --> [*]: 失败→继续修复
}
Stop --> Notification: 全部通过
Stop --> Idle: 有问题,继续
Notification --> [*]: 通知用户
通过脚本内的逻辑判断实现条件化行为:
#!/bin/bash
# .claude/hooks/conditional-hook.sh
# 演示:只在特定条件下触发的Hook逻辑
INPUT=$(cat)
EVENT=$(echo "$INPUT" | jq -r '.event // empty')
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# 条件1:CI环境下更严格
if [ "${CI:-}" = "true" ]; then
# CI中禁止所有网络操作(除了npm test)
if echo "$COMMAND" | grep -qE '(curl|wget|ssh)' && \
! echo "$COMMAND" | grep -q 'npm'; then
echo "BLOCKED: CI环境中禁止网络操作: $COMMAND"
exit 2
fi
fi
# 条件2:根据分支名调整策略
BRANCH=$(git branch --show-current 2>/dev/null || echo "HEADless")
if [[ "$BRANCH" == feature/* ]]; then
# Feature分支:宽松模式,允许实验性操作
exit 0
elif [[ "$BRANCH" == main || "$BRANCH" == release/* ]]; then
# 主干分支:严格模式
if echo "$COMMAND" | grep -qE '(npm publish|docker push|git push)'; then
echo "BLOCKED: 主干分支禁止直接发布操作"
exit 2
fi
fi
# 条件3:根据时间限制(工作时间内才允许某些操作)
HOUR=$(date +%H)
if [ "$HOUR" -lt 9 ] || [ "$HOUR" -gt 18 ]; then
if echo "$COMMAND" | grep -qE '(deploy|publish|release)'; then
echo "BLOCKED: 非工作时间禁止部署操作 (当前: ${HOUR}:00)"
exit 2
fi
fi
exit 0
通过临时文件在Hook之间传递状态:
#!/bin/bash
# .claude/hooks/state-manager.sh
# 用法: state-manager.sh [get|set] [key] [value]
STATE_DIR="/tmp/claude-hooks-state"
mkdir -p "$STATE_DIR"
ACTION="${1:-get}"
KEY="${2:-default}"
VALUE="${3:-}"
SESSION_ID="${CLAUDE_SESSION_ID:-unknown}"
STATE_FILE="$STATE_DIR/$SESSION_ID.json"
case "$ACTION" in
set)
# 设置状态
if [ ! -f "$STATE_FILE" ]; then
echo '{}' > "$STATE_FILE"
fi
jq --arg k "$KEY" --arg v "$VALUE" '.[$k] = $v' "$STATE_FILE" > "${STATE_FILE}.tmp"
mv "${STATE_FILE}.tmp" "$STATE_FILE"
;;
get)
# 获取状态
if [ -f "$STATE_FILE" ]; then
jq -r --arg k "$KEY" '.[$k] // ""' "$STATE_FILE"
fi
;;
incr)
# 计数器递增
CURRENT=$(bash "$0" get "$KEY")
NEW_VAL=$((CURRENT + 1))
bash "$0" set "$KEY" "$NEW_VAL"
echo "$NEW_VAL"
;;
esac
在Hooks中使用状态管理器:
#!/bin/bash
# 在PreToolUse中计数Bash调用
COUNT=$(bash .claude/hooks/state-manager.sh incr bash_calls)
if [ "$COUNT" -gt 50 ]; then
echo "WARNING: 本次会话已执行 $COUNT 次Bash命令,可能存在效率问题"
fi
exit 0
--bare 标志跳过Hooks自动发现和Skills加载,大幅减少启动时间:
# 正常启动(加载所有Hooks和Skills)
time claude -p "hello" --output-format text
# 约 3-5 秒
# bare模式(跳过发现阶段)
time claude -p "hello" --output-format text --bare
# 约 1-2 秒
# CI中推荐:bare + 指定必要工具
claude -p "运行测试" \
--bare \
--allowedTools "Bash,Read" \
--permission-mode acceptEdits
#!/bin/bash
# 在Hook中设置超时,防止Hook脚本挂起
TIMEOUT_SECONDS=10
# 用timeout命令包装实际逻辑
run_with_timeout() {
timeout "$TIMEOUT_SECONDS" bash -c '
INPUT=$(cat)
# ... Hook逻辑 ...
exit 0
'
EXIT_CODE=$?
if [ $EXIT_CODE -eq 124 ]; then
# 超时,默认允许(避免阻塞Agent)
echo "Hook执行超时,默认放行"
exit 0
fi
exit $EXIT_CODE
}
cat | run_with_timeout
# 启用Hook调试日志
export CLAUDE_HOOK_DEBUG=1
# 或在Hook脚本中添加调试输出到stderr
echo "[DEBUG] Hook输入: $(cat)" >&2
# 查看Hook执行历史
ls -la .claude/audit/
# 手动测试Hook脚本
echo '{"event":"PreToolUse","tool_name":"Bash","tool_input":{"command":"ls -la"}}' | \
bash .claude/hooks/pre-bash-guard.sh
echo "退出码: $?"
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Hook不触发 | Matcher拼写错误 | 检查 settings.json 中matcher是否匹配工具名(区分大小写) |
| Hook超时 | 脚本内有交互式命令 | 使用 timeout 包装,避免 read 等阻塞操作 |
-p 模式卡住 |
权限等待确认 | 添加 --permission-mode 或 --allowedTools |
| JSON输出无效 | Schema约束不匹配 | 先用 --output-format text 确认输出,再切JSON |
| Hook状态丢失 | 使用 /tmp 重启后清空 |
改用项目目录 .claude/state/ 存储状态 |
--resume 失败 |
Session ID过期 | Session有TTL,确保在有效期内恢复 |
| CI中API限流 | 并发请求过多 | 使用 xargs -P N 控制并发数,建议 N ≤ 4 |
| 维度 | 评估 |
|---|---|
| 适合训练营类型 | DevOps训练营、AI工程化训练营、团队效率提升工作坊 |
| 教学时长 | 4-6小时(含实操) |
| 难度等级 | 中高级 |
| 前置要求 | Shell脚本基础、Git操作、CI/CD概念 |
| 实操占比 | 70%(大量脚本编写和管道调试) |
| 设备要求 | 每人一台Linux/macOS终端,Claude Code API访问权限 |
| 分组建议 | 2-3人一组,每组一个真实项目仓库 |
| 考核方式 | 构建完整的CI审查管道 + Hooks质量门禁通过率 |
flowchart LR
subgraph 训练营4小时安排
A[0:00-0:30<br/>Hooks概念与七类事件] -->
B[0:30-1:30<br/>编写PreToolUse守卫<br/>PostToolUse格式化] -->
C[1:30-2:00<br/>-p模式与JSON输出] -->
D[2:00-3:00<br/>构建CI管道<br/>GitHub Actions集成] -->
E[3:00-3:30<br/>批量审查与自动修复] -->
F[3:30-4:00<br/>综合演练<br/>+ 质量门禁验收]
end
style A fill:#3498DB,color:#fff
style D fill:#E67E22,color:#fff
style F fill:#27AE60,color:#fff
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
