一、Read Hooks 概述
Claude Code 提供了一套强大的钩子系统(Hooks System),允许开发者在特定事件发生时执行自定义脚本。BeforeRead 和 AfterRead 是其中与文件读取操作紧密相关的一对钩子,分别在 Claude Code 读取文件之前和之后触发。
这两个钩子构成了一个完整的读取拦截-处理管道:BeforeRead 负责在文件内容送达 Claude 之前进行预处理(审查、过滤、转换),AfterRead 负责在读取完成后进行后处理(记录日志、统计、清理)。
核心特性:
- BeforeRead 可以修改、阻止或记录即将被读取的文件内容
- AfterRead 可以记录读取事件、统计读取频率、触发后续动作
- 两者配合可实现完整的文件读取生命周期管理
- 钩子脚本运行在宿主机环境中,拥有完整的系统访问权限
- 支持异步操作,不会阻塞 Claude Code 的核心工作流
使用场景速览
BeforeRead 可用于敏感文件内容脱敏、访问控制、内容格式转换;AfterRead 可用于读取审计日志、使用量统计、内容缓存预热等。
二、BeforeRead 钩子详解
BeforeRead 钩子在 Claude Code 执行文件读取操作之前触发。它接收即将被读取的文件路径作为输入,可以在文件内容传递给 Claude 模型之前进行干预。
2.1 触发时机
BeforeRead 在以下操作发生时触发:
- 用户通过 Read 工具显式读取文件
- Claude 自动读取上下文中的相关文件
- 工具调用过程中涉及的文件读取操作
- 配置文件中引用的文件读取
2.2 核心功能
功能一:内容审查(Content Inspection)
在文件内容到达 Claude 之前进行检查,识别是否包含敏感信息(如 API 密钥、密码、个人身份信息),并根据规则决定是否过滤或阻止读取。
功能二:访问控制(Access Control)
基于文件路径、文件名模式、项目上下文等条件,决定是否允许读取该文件。可以返回错误或空内容来阻止访问。
功能三:内容转换(Content Transformation)
在读取前对文件内容进行预处理,如替换敏感信息为占位符、转换编码格式、提取摘要等。
2.3 执行流程
- Claude Code 收到文件读取请求
- 系统检测是否配置了 BeforeRead 钩子
- 如果配置了,调用对应的钩子脚本,传入文件路径
- 钩子脚本执行处理逻辑,返回处理结果或修改后的内容
- Claude Code 根据钩子返回结果决定是否继续读取
- 如果钩子返回修改后的内容,则使用修改后的版本
三、AfterRead 钩子详解
AfterRead 钩子在 Claude Code 成功完成文件读取操作之后触发。它不参与修改文件内容,主要用于记录和监控读取行为。
3.1 触发时机
AfterRead 在以下情形触发:
- 文件读取成功完成后(无论 BeforeRead 是否修改了内容)
- 读取操作正常返回内容后立即执行
- 仅在读取成功时触发,读取失败时不会调用
3.2 核心功能
功能一:日志记录(Logging)
记录详细的读取审计信息:读取时间、文件路径、文件大小、读取用户/会话、读取频率等。
功能二:内容统计(Content Statistics)
对读取的文件进行统计分析,如代码行数、函数数量、注释比例等,用于生成使用报告。
功能三:后续动作触发(Post-processing)
根据读取的内容触发后续操作,如更新文件索引、预热缓存、通知其他系统等。
与 BeforeRead 的对比
- BeforeRead 可以修改内容;AfterRead 不能修改内容
- BeforeRead 可以阻止读取;AfterRead 只能记录读取
- BeforeRead 关注"读什么";AfterRead 关注"读了什么"
- 两者组合形成"读取前审核 + 读取后审计"的完整链路
四、配置方式
BeforeRead 和 AfterRead 钩子通过 Claude Code 的 settings.json 文件进行配置。配置文件位于项目根目录的 .claude/settings.json 或用户级别的全局配置文件中。
4.1 配置文件位置
- 项目级配置:
项目根目录/.claude/settings.json
- 用户级配置:
~/.claude/settings.json
- 本地覆盖配置:
.claude/settings.local.json(优先级最高)
4.2 配置格式
{
"hooks": {
"BeforeRead": {
"command": "node .claude/hooks/before-read.js",
"timeout": 5000
},
"AfterRead": {
"command": "python3 .claude/hooks/after-read.py",
"timeout": 3000,
"cwd": ".claude/hooks"
}
}
}
配置说明
- command: 必填,指定钩子脚本的执行命令
- timeout: 可选,脚本执行的超时时间(毫秒),默认值因钩子类型而异
- cwd: 可选,脚本执行的工作目录,默认为项目根目录
- 支持任何可执行脚本:Node.js、Python、Shell、Ruby 等
4.3 配置优先级
| 优先级 |
配置文件 |
作用范围 |
| 最高 |
.claude/settings.local.json |
本地开发环境,不提交到版本控制 |
| 中 |
.claude/settings.json |
项目级别,可提交到版本控制 |
| 低 |
~/.claude/settings.json |
用户级别,所有项目生效 |
五、钩子脚本格式
BeforeRead 和 AfterRead 钩子脚本通过标准输入输出与 Claude Code 通信。脚本接收 JSON 格式的输入数据,并返回 JSON 格式的处理结果。
5.1 输入格式
钩子脚本通过 stdin 接收一个 JSON 对象,包含以下字段:
{
"filePath": "/absolute/path/to/file.txt",
"type": "BeforeRead",
"context": {
"projectRoot": "/path/to/project",
"sessionId": "uuid-of-session"
}
}
| 字段 |
类型 |
说明 |
filePath |
string |
被读取文件的绝对路径 |
type |
string |
钩子类型,"BeforeRead" 或 "AfterRead" |
context.projectRoot |
string |
项目根目录路径 |
context.sessionId |
string |
当前会话的唯一标识 |
5.2 BeforeRead 输出格式
BeforeRead 钩子通过 stdout 输出 JSON,可以返回以下几种结果:
// 允许读取,不修改内容(返回空对象或 skip)
{ "action": "skip" }
// 允许读取,替换为修改后的内容
{
"action": "replace",
"content": "修改后的文件内容..."
}
// 阻止读取
{
"action": "block",
"reason": "此文件包含敏感信息,已被阻止读取"
}
5.3 AfterRead 输出格式
AfterRead 钩子的输出仅用于确认执行成功,不影响文件读取结果:
// 执行成功
{ "status": "ok" }
// 执行失败(仅记录日志,不影响读取)
{ "status": "error", "message": "日志写入失败" }
六、代码示例
6.1 BeforeRead 示例:敏感信息脱敏(Node.js)
const fs = require('fs');
// 读取 stdin 中的输入
let input = '';
process.stdin.on('data', chunk => { input += chunk; });
process.stdin.on('end', () => {
const { filePath } = JSON.parse(input);
const content = fs.readFileSync(filePath, 'utf-8');
// 定义敏感信息模式
const patterns = [
{ regex: /(?:api[_-]?key|apikey|secret)[:=]\s*['"]?\S+/gi,
replace: '$1: ***REDACTED***' },
{ regex: /(?:password|passwd|pwd)[:=]\s*['"]?\S+/gi,
replace: '$1: ***REDACTED***' },
{ regex: /(?:-----BEGIN.*?KEY-----)[\s\S]*?(?:-----END.*?KEY-----)/g,
replace: '***PRIVATE KEY REDACTED***' }
];
let modified = content;
for (const { regex, replace } of patterns) {
modified = modified.replace(regex, replace);
}
if (modified !== content) {
// 有敏感信息被替换
process.stdout.write(JSON.stringify({
action: 'replace',
content: modified
}));
} else {
// 无敏感信息,跳过修改
process.stdout.write(JSON.stringify({ action: 'skip' }));
}
});
6.2 BeforeRead 示例:文件访问控制(Python)
import sys, json, os
def is_allowed(file_path):
# 定义禁止读取的文件模式
blocked_patterns = [
'.env', '.env.local',
'credentials.json',
'id_rsa', 'id_ed25519',
'secrets.yaml',
'.npmrc', '.netrc'
]
filename = os.path.basename(file_path)
for pattern in blocked_patterns:
if pattern in filename:
return False, f"禁止读取敏感文件: {filename}"
return True, None
def main():
raw = sys.stdin.read()
data = json.loads(raw)
file_path = data['filePath']
allowed, reason = is_allowed(file_path)
if not allowed:
result = {"action": "block", "reason": reason}
else:
result = {"action": "skip"}
sys.stdout.write(json.dumps(result))
if __name__ == '__main__':
main()
6.3 AfterRead 示例:审计日志记录(Node.js)
const fs = require('fs');
const path = require('path');
let input = '';
process.stdin.on('data', chunk => { input += chunk; });
process.stdin.on('end', () => {
const { filePath, context } = JSON.parse(input);
const logEntry = {
timestamp: new Date().toISOString(),
file: filePath,
sessionId: context.sessionId,
projectRoot: context.projectRoot
};
const logDir = path.join(context.projectRoot, '.claude', 'logs');
const logFile = path.join(logDir, 'read-audit.log');
// 确保日志目录存在
fs.mkdirSync(logDir, { recursive: true });
// 追加日志
fs.appendFileSync(logFile,
JSON.stringify(logEntry) + '\n', 'utf-8');
process.stdout.write(JSON.stringify({ status: 'ok' }));
});
6.4 AfterRead 示例:读取统计(Shell 脚本)
#!/bin/bash
# 统计被读取文件的类型分布
read input
filePath=$(echo $input | python3 -c "import sys,json; print(json.load(sys.stdin)['filePath'])")
ext="${filePath##*.}"
statsFile=".claude/stats/read-stats.json"
mkdir -p "$(dirname "$statsFile")"
if [ ! -f "$statsFile" ]; then
echo "{}" > "$statsFile"
fi
tmp=$(mktemp)
python3 -c "
import json
with open('$statsFile') as f:
stats = json.load(f)
stats['$ext'] = stats.get('$ext', 0) + 1
with open('$statsFile', 'w') as f:
json.dump(stats, f)
"
echo '{"status":"ok"}'
脚本调试建议
钩子脚本执行时,stderr 的输出会被 Claude Code 捕获并记录到日志中。建议在开发阶段将调试信息写入 stderr:console.error('debug info')(Node.js)或 sys.stderr.write('debug info')(Python)。
七、使用场景
7.1 敏感文件脱敏(Sensitive File Redaction)
在团队协作环境中,使用 BeforeRead 钩子自动检测并替换文件中的敏感信息(API密钥、数据库密码、密钥证书等),确保 Claude 不会在回复中泄露或引用这些敏感内容。这是最广泛使用的场景。
- 自动识别常见凭证格式(环境变量、密钥文件、配置文件)
- 替换敏感值为占位符,保持文件结构完整性
- 支持正则表达式模式匹配,可自定义脱敏规则
7.2 读取审计(Read Auditing)
使用 AfterRead 钩子建立完整的文件读取审计系统,记录每一次读取操作。适用于合规要求严格的场景,如金融、医疗、政府项目。
- 记录完整的读取元数据:时间、文件、会话、用户
- 生成读取频率报告,了解 Claude 的使用模式
- 检测异常读取行为,如短时间内大量读取敏感文件
7.3 内容富化(Content Enrichment)
在读取文件前,通过 BeforeRead 钩子自动为文件添加额外的上下文信息或注释,帮助 Claude 更好地理解文件内容。
- 自动注入文件最近的 Git 提交信息(作者、时间、消息)
- 添加文件的依赖关系图或引用关系
- 插入常见的代码规范或项目约定说明
7.4 文件大小与类型控制
通过 BeforeRead 钩子对大文件进行摘要提取,或在读取二进制文件前给出警告,避免 Claude 处理不适合的内容。
| 场景 |
推荐钩子 |
实现方式 |
| 凭证脱敏 |
BeforeRead |
正则匹配替换敏感信息 |
| 访问日志 |
AfterRead |
追加日志到文件或数据库 |
| 大文件摘要 |
BeforeRead |
提取文件前 N 行或关键结构 |
| 使用统计 |
AfterRead |
统计文件类型、大小分布 |
| 内容注入 |
BeforeRead |
在文件头部附加元数据 |
| 合规审计 |
Both |
BeforeRead 过滤 + AfterRead 记录 |
八、最佳实践与注意事项
8.1 性能考虑
钩子脚本应轻量高效
- 设置合理的超时时间(timeout 参数),避免钩子长时间挂起
- 避免在 BeforeRead 中执行重量级操作(如网络请求、大型文件处理)
- 善用缓存机制,对重复读取的相同文件跳过处理
- AfterRead 日志操作应使用异步写入,不阻塞主流程
8.2 安全考量
钩子脚本拥有系统完整访问权限
- 不要在钩子脚本中硬编码敏感信息
- 谨慎处理钩子脚本的输入输出,避免注入攻击
- 定期审查钩子脚本的代码变更
settings.local.json 中的钩子配置不会提交到版本控制,适合本地调试
8.3 错误处理
- 钩子脚本应始终返回合法的 JSON 输出,即使出错也不要抛出未捕获的异常
- BeforeRead 脚本出错时,默认行为是允许读取(fail-open),不会因钩子失败而阻断工作流
- AfterRead 脚本出错时,错误仅记录在日志中,不影响文件读取操作
- 使用 stderr 输出调试信息,Claude Code 会将其记录到诊断日志中
8.4 开发与测试建议
本地开发流程
- 在
.claude/hooks/ 目录下创建脚本文件
- 先在命令行中手动测试:
echo '{"filePath":"test.txt","type":"BeforeRead"}' | node .claude/hooks/test.js
- 使用
settings.local.json 配置本地钩子,避免影响团队配置
- 确认无误后,将稳定版本迁移到项目级
settings.json
九、核心要点总结
- BeforeRead 在文件读取前触发,可审查、过滤、转换文件内容,是文件读取的安全网关
- AfterRead 在文件读取后触发,用于日志记录、统计分析和后续动作触发,是文件读取的审计员
- 配置简单: 在 settings.json 的 hooks 字段中指定命令和超时即可启用
- 标准接口: 通过 stdin/stdout 传递 JSON,支持任何编程语言
- 三动作: BeforeRead 支持 skip(跳过)、replace(替换内容)、block(阻止读取)三种响应
- fail-open 策略: 钩子执行失败不会阻塞工作流,默认允许读取
- 分层配置: 项目级、用户级、本地覆盖三级配置,灵活管理钩子行为
- 典型场景: 凭证脱敏、访问审计、大文件摘要、内容富化、合规管控
十、进一步思考
BeforeRead 和 AfterRead 钩子机制体现了 Claude Code 的可扩展性和企业级设计理念。通过这一对钩子,开发者可以在不让 Claude 直接接触敏感原始数据的前提下,充分利用其代码理解和分析能力。
在实际项目落地时,建议从最小化配置开始——先部署简单的 AfterRead 审计日志,再逐步引入 BeforeRead 的敏感信息过滤等高级功能。通过渐进式采用,可以在保障安全性的同时降低维护复杂度。
进一步探索方向
- 结合 BeforeRead + AfterRead 实现文件读取的"断路器"模式(异常检测 → 自动阻断)
- 与 CI/CD 系统集成,将审计日志上报到集中式日志平台(ELK、Datadog)
- 利用 BeforeRead 实现多语言编码格式自动检测与转换
- 基于 AfterRead 统计数据分析团队使用模式,优化项目文件结构