BeforeRead / AfterRead 钩子详解

Claude Code 文件读取前后触发钩子——从配置到实战的完整指南

主题: Claude Code BeforeRead / AfterRead 钩子

核心内容: 概述、配置方法、脚本格式、使用场景、代码示例、最佳实践

关键词: BeforeRead, AfterRead, Hooks, 文件读取, 内容审查, 审计日志, 敏感信息过滤, 内容转换

一、Read Hooks 概述

Claude Code 提供了一套强大的钩子系统(Hooks System),允许开发者在特定事件发生时执行自定义脚本。BeforeReadAfterRead 是其中与文件读取操作紧密相关的一对钩子,分别在 Claude Code 读取文件之前和之后触发。

这两个钩子构成了一个完整的读取拦截-处理管道:BeforeRead 负责在文件内容送达 Claude 之前进行预处理(审查、过滤、转换),AfterRead 负责在读取完成后进行后处理(记录日志、统计、清理)。

核心特性:

  • BeforeRead 可以修改、阻止或记录即将被读取的文件内容
  • AfterRead 可以记录读取事件、统计读取频率、触发后续动作
  • 两者配合可实现完整的文件读取生命周期管理
  • 钩子脚本运行在宿主机环境中,拥有完整的系统访问权限
  • 支持异步操作,不会阻塞 Claude Code 的核心工作流

使用场景速览

BeforeRead 可用于敏感文件内容脱敏、访问控制、内容格式转换;AfterRead 可用于读取审计日志、使用量统计、内容缓存预热等。

二、BeforeRead 钩子详解

BeforeRead 钩子在 Claude Code 执行文件读取操作之前触发。它接收即将被读取的文件路径作为输入,可以在文件内容传递给 Claude 模型之前进行干预。

2.1 触发时机

BeforeRead 在以下操作发生时触发:

2.2 核心功能

功能一:内容审查(Content Inspection)

在文件内容到达 Claude 之前进行检查,识别是否包含敏感信息(如 API 密钥、密码、个人身份信息),并根据规则决定是否过滤或阻止读取。

功能二:访问控制(Access Control)

基于文件路径、文件名模式、项目上下文等条件,决定是否允许读取该文件。可以返回错误或空内容来阻止访问。

功能三:内容转换(Content Transformation)

在读取前对文件内容进行预处理,如替换敏感信息为占位符、转换编码格式、提取摘要等。

2.3 执行流程

  1. Claude Code 收到文件读取请求
  2. 系统检测是否配置了 BeforeRead 钩子
  3. 如果配置了,调用对应的钩子脚本,传入文件路径
  4. 钩子脚本执行处理逻辑,返回处理结果或修改后的内容
  5. Claude Code 根据钩子返回结果决定是否继续读取
  6. 如果钩子返回修改后的内容,则使用修改后的版本

三、AfterRead 钩子详解

AfterRead 钩子在 Claude Code 成功完成文件读取操作之后触发。它不参与修改文件内容,主要用于记录和监控读取行为。

3.1 触发时机

AfterRead 在以下情形触发:

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 配置文件位置

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 性能考虑

钩子脚本应轻量高效

  1. 设置合理的超时时间(timeout 参数),避免钩子长时间挂起
  2. 避免在 BeforeRead 中执行重量级操作(如网络请求、大型文件处理)
  3. 善用缓存机制,对重复读取的相同文件跳过处理
  4. AfterRead 日志操作应使用异步写入,不阻塞主流程

8.2 安全考量

钩子脚本拥有系统完整访问权限

  • 不要在钩子脚本中硬编码敏感信息
  • 谨慎处理钩子脚本的输入输出,避免注入攻击
  • 定期审查钩子脚本的代码变更
  • settings.local.json 中的钩子配置不会提交到版本控制,适合本地调试

8.3 错误处理

8.4 开发与测试建议

本地开发流程

  1. .claude/hooks/ 目录下创建脚本文件
  2. 先在命令行中手动测试:echo '{"filePath":"test.txt","type":"BeforeRead"}' | node .claude/hooks/test.js
  3. 使用 settings.local.json 配置本地钩子,避免影响团队配置
  4. 确认无误后,将稳定版本迁移到项目级 settings.json

九、核心要点总结

十、进一步思考

BeforeRead 和 AfterRead 钩子机制体现了 Claude Code 的可扩展性企业级设计理念。通过这一对钩子,开发者可以在不让 Claude 直接接触敏感原始数据的前提下,充分利用其代码理解和分析能力。

在实际项目落地时,建议从最小化配置开始——先部署简单的 AfterRead 审计日志,再逐步引入 BeforeRead 的敏感信息过滤等高级功能。通过渐进式采用,可以在保障安全性的同时降低维护复杂度。

进一步探索方向

  • 结合 BeforeRead + AfterRead 实现文件读取的"断路器"模式(异常检测 → 自动阻断)
  • 与 CI/CD 系统集成,将审计日志上报到集中式日志平台(ELK、Datadog)
  • 利用 BeforeRead 实现多语言编码格式自动检测与转换
  • 基于 AfterRead 统计数据分析团队使用模式,优化项目文件结构