时间过得真快,距离上次发话题已经过去几个月,成年人的时间真是不经用。马上过年了,想罢年前一定要发点东西出来的。预祝大家新年快乐。


老实说,我在使用 AI coding 时,最抓狂的不是它写不出代码,而是它 太喜欢 “一口闷” 了

场景通常是这样的:
我让 Claude Code 查一个 Bug,它二话不说读取了一个 5000 行的 server.log 或者把整个 utils.py 塞进上下文。结果就是:

  1. Token 燃烧:我的钱包在滴血。
  2. 上下文污染:关键信息被淹没在几千行无关代码里,它的智商瞬间掉线,开始胡言乱语。
  3. 响应变慢:处理大量 Token 需要时间。

这其实就是典型的 上下文过载(Context Overload。模型就像一个贪婪的读者,如果你不限制它,它会试图把整个图书馆搬回家,而不是只借那一本它需要的书。

最近我在研究 Anthropic 提倡的 渐进式披露(Progressive Disclosure),并折腾出了一套强制性的文件读取策略。今天分享给大家,亲测能让 Claude Code 的脑子清醒不少。

什么是 “渐进式披露”?

别被这个学术名词吓到。用人话说是:不要给 AI 看全图,除非它问你要。

这就好比你作为一个人类程序员接手新项目,你不会上来就把 10 万行代码从头读到尾。你会先看目录结构(ls),再搜关键字(grep),最后只打开相关的那几十行代码(read)。

Anthropic 的文档里一直强调这一点:让模型先通过搜索定位,再通过切片读取。

但在实际的 CLI 工具中,Claude 有时候很懒,或者说 “太勤快”,默认行为往往是直接 Read 全文。所以,我们需要给它装一个 “防呆开关”。

这个 Hook 是怎么工作的?

我写了一个 Python 脚本作为 PreToolUse 的 Hook(工具调用前拦截器),配合 CLAUDE.md 的提示词,搞了一套 软硬兼施 的组合拳。

核心逻辑

这个方案由两部分组成:

  1. “软” 规则(Prompt):在系统提示词里告诉它,读文件必须加 offset(起始行)和 limit(行数限制)。
  2. “硬” 拦截(Hook 脚本):这是关键。当 Claude Code 试图调用 Read 工具时,脚本会检查目标文件的大小。
  • 如果文件超过 1000 行,且 Claude Code 没有 指定 offset/limit
  • 拦截操作!返回 Exit Code 2。
  • 杀手锏:在 stderr 里返回一段精心设计的报错信息。这段报错不仅告诉它 “你错了”,还告诉它 “你应该怎么做”(比如:推荐你先用 Grep 搜一下,然后只读第 X 行附近的 50 行)。

为什么它非常 Work?

这利用了 LLM 的一个特性:它们非常听 “报错信息” 的话。

当 Tool Use 失败并返回一个明确的 “推荐路径” 时,Claude 会立刻在这个报错的 Context 下进行自我修正。

  • Claude: “我要读 app.log。” (未指定范围)
  • Hook: (拦截) “不行,文件太大了。你必须指定读取范围。建议先用 Grep 搜一下关键词。”
  • Claude: (收到报错) “噢,抱歉。那我们就先用 Grep 搜一下 ‘Error’ 关键字吧。”

看,这就强行把它拽回了 “渐进式披露” 的最佳实践路径上。

如何食用

你需要两个东西:一个是配置在项目根目录的规则文件,一个是实际执行拦截的 Python 脚本。

1. 提示词 (CLAUDE.md)

把这段加到你的项目提示词文件中。这相当于 “先礼后兵”,先告诉它规则。

中文版本

### 文件读取策略

** 强制规则 **:每次调用 Read 工具时 ** 必须 ** 指定 `offset``limit` 参数,禁止使用默认值。

#### 参数要求

| 参数   | 要求           | 说明                          |
| ------ | -------------- | ----------------------------- |
| `offset` | ** 必须指定 ** | 起始行号(从 0 开始)         |
| `limit`  | ** 必须指定 ** | 读取行数,单次不超过 500 行   |

#### 读取流程 1. ** 侦察 **:先用 Grep 了解文件结构,或定位目标关键词行号。
2. **
精准打击 **:使用 offset + limit 精确读取目标区域。
3. **
扩展 **:如果需要更多上下文,再调整 offset 继续读取。

**
目标 **:保持上下文精准、最小化。如果不遵守,工具调用将被 Hook 拦截。

English Version

### File Reading Strategy **MANDATORY RULE**: Every `Read` tool call **MUST** verify `offset` and `limit` parameters. Default full-file reads are prohibited for non-trivial files.

#### Parameter Requirements

| Param    | Requirement    | Description                   |
| -------- | -------------- | ----------------------------- |
| `offset` | **REQUIRED** | Start line number (0-indexed) |
| `limit`  | **REQUIRED** | Max lines to read (Max 500)   |

#### Workflow 1. **Recon**: Use `Grep` first to understand structure or locate keywords.
2. **Surgical Read**: Use `offset` + `limit` to read only the relevant section.
3. **Expand**: Adjust `offset` to read more context only if strictly necessary.

**Goal**: Keep context precise and minimal. Violations will be blocked by the PreToolUse hook.

2. The Hook (Python 脚本)

保存为 read_limit_hook.py,并在你的 Claude CLI 配置 hook(如果你不会可以直接把文件给 claude code 让它代劳)。

(这个脚本稍微有点长,但逻辑很简单:检查文件大小 → 检查参数 → 决定是放行、自动修正还是报错拦截)

#!/usr/bin/env python3 """
PreToolUse hook for Read tool - Enforce offset/limit and block large file reads.
"""
import json import sys import os from datetime import datetime # --- 配置区域 --- MAX_FILE_LINES = 1000 # 超过这个行数必须切片读 MAX_FILE_BYTES = 50 * 1024 MAX_SINGLE_READ_LINES = 500 # 一次最多读 500 行 MAX_SINGLE_READ_BYTES = 20 * 1024 # 跳过不需要检查的二进制文件 SKIP_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.pdf', '.exe', '.dll', '.so', '.dylib', '.zip', '.tar', '.gz'} # 日志文件(可选,帮你分析它浪费了多少次尝试) LOG_FILE = os.path.expandvars ("$USERPROFILE/.claude/hooks/read-stats.log") def get_file_stats (file_path): try: if not os.path.exists (file_path): return None, None size = os.path.getsize (file_path) with open (file_path, 'r', encoding='utf-8', errors='ignore') as f: lines = sum (1 for _ in f) return lines, size except: return None, None def format_bytes (size): if size >= 1024 * 1024: return f"{size / (1024 * 1024):.1f} MB" if size >= 1024: return f"{size / 1024:.1f} KB" return f"{size} B" def main (): try: input_data = json.load (sys.stdin) except: sys.exit (0) # 甚至不是 JSON,不管了 tool_name = input_data.get ("tool_name", "") tool_input = input_data.get ("tool_input", {}) # 只管 Read 工具 if tool_name != "Read": sys.exit (0) file_path = tool_input.get ("file_path", "") offset = tool_input.get ("offset") limit = tool_input.get ("limit") # 1. 扩展名检查 ext = os.path.splitext (file_path)[1].lower () if ext in SKIP_EXTENSIONS: sys.exit (0) lines, size = get_file_stats (file_path) if lines is None: sys.exit (0) # 读不到文件,让 Claude 自己处理错误 # 2. 检查是否是大文件 is_large_file = lines > MAX_FILE_LINES or size > MAX_FILE_BYTES if is_large_file: # 如果是大文件,且没有指定 offset 或 limit -> 拦截! if offset is None or limit is None: reason = f"{lines} lines / {format_bytes (size)}" error_msg = ( f"BLOCKED: File is too large ({reason}) for a full read.\n" f"You MUST use 'offset' and 'limit' to read specific sections.\n\n" f"Strategy:\n" f"1. Use`Grep`to find the line number of your function/variable.\n" f"2. Then`Read`with offset=LINE_NUM, limit=50.\n" f"DO NOT try to read the whole file again." ) print (error_msg, file=sys.stderr) sys.exit (2) # 2 通常表示操作被拒绝 # 3. 检查单次读取是否贪得无厌 if limit is not None and limit > MAX_SINGLE_READ_LINES: print (f"BLOCKED: Limit {limit} is too high. Max allowed is {MAX_SINGLE_READ_LINES}.", file=sys.stderr) sys.exit (2) # 4. 贴心的小功能:如果有 offset 没 limit,自动帮它补上 limit,防止它犯傻 if offset is not None and limit is None: output = { "hookSpecificOutput": { "permissionDecision": "allow", "updatedInput": { "limit": MAX_SINGLE_READ_LINES } } } print (json.dumps (output)) sys.exit (0) sys.exit (0) if __name__ == "__main__": main ()

效果

装上这一套之后,你会发现 Claude 的行为模式变了:

  • 以前:读取 main.c (3000 行) → 思考 → 修改。
  • 现在:尝试读取 main.c → 被拦截 → 思考 “哦,我应该先搜一下” → Grep main 函数 → 读取 main 函数周围 50 行 → 思考 → 修改。

虽然多了一步交互,但 上下文极其干净,Token 消耗量能降低 80% 以上,而且修改的准确率反而提高了。

试一下吧,让你的 Claude Code 甚至其他 Agent 学会渐进式的读取。


📌 转载信息
原作者:
cedricthecoder
转载时间:
2026/1/22 12:56:48

标签: AI编程, claude code, 渐进式披露, 上下文过载, PreToolUse Hook

添加新评论