本教程教你把 Claude Code 从"你敲一行、它回一句"的交互式工具,改造成一个被外部事件驱动的常驻 Agent:文件一保存、日志一出错、定时一到点,它就自动接管任务、调用子代理、跑完闭环。核心组合是 watchexec/entr 文件监听 + -p 头部模式 + --output-format stream-json 流式输出 + Stop/PostToolUse 钩子防跑飞。适合想用 Claude Code 搭建"代码守护进程""日志自愈机器人""保存即审查"流水线的开发者和 DevOps 工程师。
claude --version 能输出版本号),完成一次登录或配置好 ANTHROPIC_API_KEYjq(解析 JSON 输出)、fzf(交互式选择)传统 Claude Code 用法是交互式的:打开终端、输入提示、来回迭代。事件驱动自动化则把这个模型翻转过来——没有人类在键盘前,取而代之的是一个外部事件源(文件变化、cron、webhook、日志行),事件一发生就拉起一次 claude -p 调用。
这套模型有三个关键差异点:
--output-format json 或 stream-json,让下游脚本能解析。事件驱动的"事件源",最常用的是文件系统变化。watchexec 是跨平台首选,entr 更轻量。
先安装监听器:
# macOS
brew install watchexec entr jq
# Linux (Debian/Ubuntu)
sudo apt install entr jq
cargo install watchexec-cli # 或从 release 下载二进制
# 验证
watchexec --version && jq --version
最朴素的"保存即审查"可以这样跑起来——在项目根目录执行:
# 监听所有 .py 文件变化,一变就审查
watchexec -e py -- claude -p "审查 src/ 下最近修改的 Python 文件,指出潜在 bug,输出 JSON:{file, issues:[{line, severity, message}]}"
预期结果:你随便改一个 src/foo.py 并保存,watchexec 检测到变化,立刻拉起一次 claude -p 调用,几秒后终端打印出该文件的审查结果。每保存一次就跑一次,全程无需你按回车。
常见问题:
watchexec 默认有约 100ms 防抖,若编辑器写入是"先删后建"可能触发不到。加 --debounce 300 放宽窗口,或用 -w src 显式锁定监听目录。claude -p 默认看不到"哪个文件刚变"。要精确,把文件名通过环境变量或参数传进去——下一步用脚本封装时会解决。裸用 watchexec -- claude -p 太粗。真正的事件驱动需要把事件元数据喂给 Agent。下面这个脚本 monitor.sh 是生产可用的最小骨架:
#!/usr/bin/env bash
# monitor.sh —— 文件变化驱动的自动审查守护进程
set -euo pipefail
WATCH_DIR="${1:-src}"
EVENT_FILE="$2" # watchexec 会把变化的文件路径写进这个变量
# 关键:用 --bare 跳过 hooks/MCP 自动发现,启动更快,守护进程更稳
# 用 --output-format json 拿结构化结果,用 --add-dir 让 Agent 能读全项目上下文
claude --bare -p \
--add-dir "$WATCH_DIR" \
--output-format json \
"我刚修改了文件:$EVENT_FILE。
请:
1. 读这个文件
2. 找出 bug、坏味道、安全问题
3. 如果有严重问题(severity=critical),用 Edit 工具直接修复
4. 最后用 JSON 输出摘要:{file, fixed:[bool], issues:[...], summary}" \
| jq '.' # 格式化输出,方便人眼和下游管道读取
让 watchexec 把变化文件路径传进来:
watchexec -w src -e py \
--shell=none \
-- ./monitor.sh src "{$WATCHEXEC_COMMON_PATH}"
这里 {$WATCHEXEC_COMMON_PATH} 是 watchexec 的占位符,会被替换成实际变化的文件相对路径。
预期结果:保存 src/api.py,终端先打印一段 JSON,其中 fixed: true 且列出修复的问题——说明 Agent 不只是审查,还自动改了代码。
常见问题:
{$WATCHEXEC_COMMON_PATH} 没被替换、原样传入:那是 watchexec 占位符语法,必须配合 --shell=none 才生效;若用默认 shell 模式,要改用环境变量 WATCHEXEC_CHANGED_PATHS 在脚本内 $1 读取。--debounce 500 合并 500ms 内的所有事件为一次。json 模式是一次性等到底才出结果,长任务(自动修 bug 可能要几十秒)期间终端一片黑。事件驱动场景下你往往想看到实时进度,用 stream-json:
claude --bare -p \
--output-format stream-json \
--verbose \
"修复 src/auth.py 里的所有 lint 错误" \
| jq --unbuffered -r 'select(.type=="tool_use") | "🔧 调用工具: \(.name) → \(.input.file_path // "")"'
stream-json 会逐行吐出 JSON 事件:system(初始化)、assistant(模型思考)、tool_use(要调工具)、tool_result(工具返回)、result(最终结果)。用 jq 过滤 tool_use 事件,就能实时打印"Agent 正在读哪个文件、改哪个文件",相当于一个简易进度监控。
预期结果:终端像日志流一样滚动输出 Agent 的每一步动作,而不是干等几十秒。
事件驱动 Agent 最大的风险是失控循环:Agent 改了文件 → 触发文件监听 → 又拉起一次 Agent → 又改文件……死循环。Claude Code 的 hooks 是确定性护栏,不依赖模型"自觉"。
在 .claude/settings.json 配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash .claude/guards/no-overwrite-guard.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "ruff format \"$(jq -r '.tool_input.file_path' )\" 2>/dev/null || true"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/guards/stop-queue.sh"
}
]
}
]
}
}
三个钩子各司其职:
ruff format,保证改完的代码总是格式化的。护栏脚本 .claude/guards/no-overwrite-guard.sh:
#!/usr/bin/env bash
# 阻止 Agent 覆盖受保护文件(如锁文件、生成产物)
INPUT="$(cat)"
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')
# 保护模式:禁止动这些
if [[ "$FILE_PATH" =~ (package-lock\.json|\.env|dist/|build/) ]]; then
echo "禁止修改受保护文件: $FILE_PATH" >&2
exit 2 # exit code 2 = 阻止该工具调用,把 stderr 反馈给 Agent
fi
exit 0 # exit code 0 = 放行
关于 Stop hook 的硬限制(必须知道):Claude Code 规定,如果 Stop hook 连续 8 次让 Agent 继续跑(即不停让模型"再做一轮"),第 9 次会被强制打断。这是为了防止无限循环。设计 Stop hook 时不要试图用它实现"永远不停",而是用来排空一个有限的任务队列。
真正的事件驱动不是"跑一次就停",而是"一个事件触发后,Agent 自己把一组相关任务跑完"。这需要把待办任务持久化到文件,用 Stop hook 检查队列。
任务队列设计——/tmp/cc-tasks.txt,一行一个任务:
审查 src/auth.py 并修复 lint
审查 src/api.py 并修复 lint
跑 pytest 并修复失败的用例
Stop hook 脚本 .claude/guards/stop-queue.sh:
#!/usr/bin/env bash
# Agent 想停时,检查是否还有未完成任务
QUEUE="/tmp/cc-tasks.txt"
if [[ ! -s "$QUEUE" ]]; then
# 队列空,真正结束
exit 0
fi
# 弹出队首任务,喂回给 Agent 让它继续
NEXT_TASK=$(sed -n '1p' "$QUEUE")
sed -i '1d' "$QUEUE" # 删掉队首
# 通过 JSON decision 让 Agent 继续,并注入新任务
jq -n --arg t "$NEXT_TASK" '{decision: "block", reason: "队列未空,继续处理下一个任务:\($t)"}'
这里 decision: "block" 配合 reason 是 Stop hook 的约定:告诉 Claude Code"别停,这是原因,继续做"。Agent 读到 reason 后会接着干下一个任务。配合前述 8 次硬上限,单个事件最多驱动 8 轮——足够处理一个小队列,又不会失控。
codewatch把前五步组装成一个完整的、可运行的守护进程——监听 src/,文件一变就让子代理审查并修复,跨文件问题丢进队列串行处理。
目录结构:
my-project/
├── src/
│ ├── auth.py
│ └── api.py
├── .claude/
│ ├── settings.json
│ ├── agents/
│ │ └── code-reviewer.md # 子代理:专门做审查
│ └── guards/
│ ├── no-overwrite-guard.sh
│ └── stop-queue.sh
└── codewatch.sh # 守护进程入口
子代理定义 .claude/agents/code-reviewer.md(事件驱动常配子代理,让主 Agent 专注编排):
---
name: code-reviewer
description: 审查单个 Python 文件,找出 bug 和安全问题,必要时直接修复。当需要审查代码改动时调用。
tools: Read, Edit, Grep
---
你是资深 Python 代码审查员。审查时:
1. 先通读整个文件,理解意图
2. 按 severity(critical/high/medium/low) 标注问题
3. critical 和 high 直接用 Edit 修复
4. 输出 JSON:{file, fixed_issues, remaining_issues, summary}
守护进程入口 codewatch.sh:
#!/usr/bin/env bash
# codewatch —— 文件变化驱动的自愈代码守护进程
set -euo pipefail
WATCH_DIR="${1:-src}"
QUEUE="/tmp/cc-tasks-$(basename "$PWD").txt"
: > "$QUEUE" # 清空队列
echo "👁 守护进程启动,监听 $WATCH_DIR (Ctrl+C 退出)"
# 初始化队列 + 关键:把队列路径告诉 Stop hook
export CC_QUEUE="$QUEUE"
watchexec -w "$WATCH_DIR" -e py --debounce 600 --shell=none -- \
bash -c '
FILE="{$WATCHEXEC_COMMON_PATH}"
# 把任务压入队列(保持单一 Python 审查任务串行,避免并发抢同一文件)
echo "用 code-reviewer 子代理审查并修复 $FILE" >> "'"$QUEUE"'"
# 拉起 Agent,它会读到队列里的任务,靠 Stop hook 逐个消化
claude --bare -p \
--add-dir "'"$WATCH_DIR"'" \
--permission-mode acceptEdits \
"处理 /tmp 队列里的代码审查任务。每个任务委托 code-reviewer 子代理完成。"
'
启动它:
chmod +x codewatch.sh .claude/guards/*.sh
./codewatch.sh src
然后去编辑器里改坏一个文件(比如在 src/auth.py 故意留个未关闭的文件句柄、加个 eval(user_input)),保存。你会观察到:
watchexec 检测到 .py 变化,压一个任务进队列claude --bare -p 启动,读到队列,派 code-reviewer 子代理审查eval 漏洞,用 Edit 修复;PostToolUse hook 自动 ruff format这就是事件驱动的完整闭环:人类只负责"保存文件"这一个动作,审查、修复、格式化、串行调度全自动化。
flowchart TD
A[人类保存 src/auth.py] --> B[watchexec 检测文件变化]
B --> C[任务压入 /tmp 队列]
C --> D["claude --bare -p 启动<br/>读取队列"]
D --> E[PreToolUse 护栏: 拦截危险文件]
E --> F[委托 code-reviewer 子代理]
F --> G[子代理审查+Edit修复]
G --> H[PostToolUse: ruff 自动格式化]
H --> I{Agent 想停}
I -->|Stop hook 查队列| J{队列还有任务?}
J -->|是| K[弹出队首 注入继续] --> F
J -->|否| L[真正退出 等待下次事件]
K -.8次上限.-> M[强制熔断 防跑飞]
style A fill:#e1f5e1
style L fill:#e1f5e1
style M fill:#ffe1e1
style E fill:#fff3e0
style H fill:#fff3e0
单个文件监听器只是起点。真实场景会接多个事件源:
0 2 * * * /run-audit.sh 每天凌晨跑全仓库安全扫描,结果写进队列,Agent 醒来消化。netcat 或轻量 HTTP server 收 GitHub PR webhook,落盘成任务文件,触发 claude -p。tail -f app.log | grep --line-buffered ERROR | while read line; do echo "调查日志错误: $line" >> "$QUEUE"; done——日志一出错,Agent 自动来查。并发隔离用 git worktree:多个事件同时到达时,给每个任务开一个临时 worktree,避免改同一文件打架(这正是"worktree 并行扇出批量跑任务"的思路,事件驱动里把它作为并发后端)。注意 worktree 模式下 hooks 配置和 .claude/agents 仍从主仓库读取,护栏一致。
无人值守最怕"不知道它刚才干了啥"。三个排查手段:
--output-format stream-json | jq,每个 tool_use 都可见。codewatch.sh 里把每次 claude -p 的输出 tee 到带时间戳的日志:
bash
claude --bare -p ... | tee -a "logs/$(date +%Y%m%d-%H%M%S).json"exit code 2 才是"阻止并反馈",其他非零退出码只是"hook 出错但放行"——别搞混,否则护栏形同虚设。-p、--bare、--output-format 全量参数。-p/--bare/JSON 输出格式的权威参数说明。-p"两节课之后,作为把二者组装成系统的综合实战。[!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 等场景全覆盖。
