文件读取门¶
它是什么¶
文件读取门会阻止 Claude Code 读取 claude-mem 已对其有观察的整个代码文件。Claude 改为获取该文件的观察时间线,并附有指向成本更低的信息来源的指引:文件结构用 smart_outline,单个符号用 smart_unfold,过往工作用 get_observations,精确行用带 offset 和 limit 的 Read。
该门控默认开启。参见如何关闭它。
这是渐进式披露在文件读取上的应用:先展示已有什么,再让 Claude 只获取它需要的内容。
何时拦截读取¶
以下条件必须全部成立:
- 会话是 Claude Code(而非 Qwen Code),且 Read 来自主会话,而非子代理。
- 该文件是
smart_outline可解析的代码(见下表)。 - 该文件位于项目内,即解析符号链接后位于会话的工作目录之下。
smart_outline和smart_unfold也会执行同样的检查,因此项目中指向项目外部的符号链接永远不会被拦截:这些工具会拒绝它。 - 文件至少为 32 KB。 有历史的较小文件会将时间线作为上下文,且永远不会被拦截。参见大小阈值。
- 文件有观察历史,并且自其中最新的观察以来未被修改。
- Read 将返回整个文件。 对文件部分内容的 Read 始终放行。参见整文件读取与定向读取。
- 驱动
smart_outline和smart_unfold的 tree-sitter CLI 已安装。 若没有它,Read 会带着时间线作为上下文放行,因此 Claude 永远不会被引导到一个无法解析该文件的工具。Claude Code 的插件安装不会放入这个可执行文件,因此 claude-mem worker 在每次启动缺少它时,都会在后台从 tree-sitter 的 GitHub Releases 下载它。worker 只会运行或安装 SHA-256 与 claude-mem 为该 tree-sitter 版本和平台锁定的摘要相匹配的下载。在新安装中,门控会在第一次会话开始几秒后启用。如果下载无法访问 GitHub,worker 日志会说明原因;一旦网络允许,运行npx claude-mem repair。
门控覆盖的代码文件:
| 语言 | 扩展名 |
|---|---|
| JavaScript | .js .mjs .cjs .jsx |
| TypeScript | .ts .mts .cts .tsx |
| Python | .py .pyw |
| Go | .go |
| Rust | .rs |
| Ruby | .rb |
| Java | .java |
| Kotlin | .kt .kts |
| Scala | .scala .sc |
| Swift | .swift |
| C 和 C++ | .c .h .cpp .cc .cxx .hpp .hh |
| PHP | .php |
| Lua | .lua |
| Haskell | .hs |
| Zig | .zig |
| Shell | .sh .bash .zsh |
| CSS 和 SCSS | .css .scss |
| SQL | .sql |
Markdown、YAML、TOML、JSON 以及所有其他文件类型永远不会被拦截。Read 才是适合它们的工具。
Qwen Code 会运行 Claude Code 的钩子,Codex CLI 和 Kimi 也会运行同一个钩子,但它们都不会拦截:钩子会将文件的时间线作为上下文添加,并让调用放行。其他宿主不会运行此钩子。
工作原理¶
Claude calls Read(file_path="/Users/you/code/acme-api/src/billing/invoice-service.ts")
↓
PreToolUse hook runs (synchronous: the Read waits for it)
↓
Subagent? ────────────────────────────────→ Read proceeds, nothing added
↓ No
Project excluded? ────────────────────────→ Read proceeds, nothing added
↓ No
File under 1,500 bytes? ──────────────────→ Read proceeds, nothing added
↓ No
Query worker: GET /api/observations/by-file (3 s budget)
↓
Worker slow or down? ─────────────────────→ Read proceeds, nothing added
↓ No
No observations? ─────────────────────────→ Read proceeds, nothing added
↓ Has observations
File modified since the newest one? ──────→ Read proceeds, nothing added
↓ No
Keep 1 per session, rank by specificity, keep up to 15
↓
Claude Code (not Qwen)? ────── No ──┐
Gate setting on? ───────────── No ──┤
Code file? ─────────────────── No ──┤
At least 32 KB? ────────────── No ──┤
Inside the project? ────────── No ──┼──→ Read proceeds, timeline added as context
Whole-file Read? ───────────── No ──┤ (once per session per file)
Tree-sitter CLI installed? ─── No ──┘
↓ Yes to all seven
DENY the Read: Claude gets the timeline and where to look instead
当 Read 未被拦截但文件有当前历史时,时间线会作为上下文添加到 Read 中:每个文件每个会话一次,并且只有在该文件出现了更新的观察后才会再次添加。在图中更早停住的读取不会添加任何内容。
对被门控文件的每次整文件 Read 都会被拦截,而不仅仅是第一次。
Claude 看到什么¶
当门控拦截 Read 时,Claude Code 会取消它,并改而向 Claude 提供这条消息。在会话记录中,它显示为 PreToolUse:Read hook error;这正是门控在工作,而非故障。
Current: 2026-10-05 3:25pm PDT
Full-file Read blocked by claude-mem: /Users/you/code/acme-api/src/billing/invoice-service.ts has prior observations (listed below). Get what you need without reading the whole file:
- Current code: call smart_outline(file_path="/Users/you/code/acme-api/src/billing/invoice-service.ts") for its symbols and line numbers, then smart_unfold(file_path="/Users/you/code/acme-api/src/billing/invoice-service.ts", symbol_name="<name>") for the ones you need (MCP tools mcp__plugin_claude-mem_mcp-search__smart_outline / __smart_unfold; load them with ToolSearch if they are deferred).
- Past work: call get_observations(ids=[41890, 42298, 42301]) for the observations below that matter (~300 tokens each).
- Exact lines, e.g. before an Edit: call Read on this file again with offset and limit around the lines smart_outline reported. Partial reads are allowed and satisfy Edit's read requirement.
The titles below are history from earlier sessions, not the file's current contents.
### Sep 28, 2026
41890 4:30p 🟣 Added proration for mid-cycle plan changes
### Oct 3, 2026
42298 1:50p 🔄 Split invoice totals into calculateSubtotal and applyTaxes
42301 2:15p 🔴 Fixed rounding error in multi-currency invoice totals
每条时间线行格式为 <id> <time> <type> <title>,按天分组,从旧到新。类型图标:🔴 修复,🟣 功能,🔄 重构,🔵 发现,⚖️ 决策,✅ 变更。
Note
该拦截信息是给 Claude 看的,不是给你看的。没有需要点击通过的审批提示。若要让整文件读取通过,请关闭该门控。
整文件读取与定向读取¶
| 读取调用 | 结果 |
|---|---|
Read(file_path),不带 offset 或 limit |
被拦截 |
offset 为 0 或 1(或未提供),且 limit 至少等于文件行数 |
被拦截:会返回整个文件 |
limit 小于文件行数 |
允许 |
offset 大于 1 |
允许 |
定向读取是 Claude 获取 Edit 所需精确行的方式。Claude Code 的 Edit 工具拒绝修改会话中尚未被 Read 过的文件,而对文件的部分读取即可满足该要求。典型的编辑流程:
Read(file_path):被拦截。Claude 会看到时间线和上面的指示。smart_outline(file_path):每个符号及其行范围。Read(file_path, offset=120, limit=40):允许。仅需要修改的函数。Edit(...):可正常执行,因为第 3 步已对该文件进行过 Read。
拦截后紧接着的定向 Read 不会重复显示时间线,因为拦截信息中已经展示过了。
决策树¶
拦截后,Claude 的选项按成本从低到高排列:
| 选项 | Token 成本 | 使用时机 |
|---|---|---|
| 仅时间线(语义提示) | 0 额外 | 标题已足够让 Claude 继续操作 |
| get_observations(ids=[...]) | 每个约 300 | 需要过往工作的详细信息 |
| smart_outline | 约 1-2k | 需要文件当前的结构和行号 |
| smart_unfold | 每个符号约 400-2k | 需要完整查看某个函数或类 |
| 带 offset 和 limit 的 Read | 仅所读取的行 | 需要精确行,例如在 Edit 之前 |
| 整文件 Read | 整个文件 | 门控开启时会被拦截:使用 offset 和 limit 读取所需的片段 |
智能工具的成本来自 Smart Explore 基准测试。
用于时间推理的当前日期/时间¶
该消息以当前日期和时间开头:
这让 Claude 能判断每条观察有多旧。该格式与 SessionStart 上下文头(YYYY-MM-DD time timezone)一致,因此 Claude 在整个会话中看到相同的时间标记。
当文件在其最新观察之后被修改时,门控会主动让路,因此被拦截的文件自那次观察以来没有发生变化。较旧的标题仍可能描述代码的早期版本,这就是为什么该消息将它们标记为历史,而非文件当前内容。
Token 成本¶
完整读取大型源文件需消耗数千到数万 Token。取而代之的拦截消息成本如下:
| 组件 | Token 数 |
|---|---|
| 标题和指示 | ~250(路径出现三次,长路径会略微增加) |
| 每条观察行 | ~20 |
| 达到 15 条观察上限时的完整消息 | ~550-600 |
如果 Claude 需要更多细节,通过 get_observations 获取每条观察约需 300 Token。
实际示例¶
在本仓库的 src/services/worker-service.ts(1,932 行)中修改 WorkerService.shutdown()。
无门控时:
有门控时:
Blocked Read: deny message (5 observations) ~360 tokens
smart_outline ~1,340 tokens
Read offset=1006 limit=67 (shutdown only) ~890 tokens
Total ~2,600 tokens (about 88% less)
本页的 Token 计数为按每个 Token 4 个字符估算的近似值。
特异性排名¶
Worker 最多返回该文件的 40 条观察,按最新在前排列。门控每个会话仅保留一条(最新的),并根据每条观察与当前文件的关联特异度进行评分,保留分数最高的 15 条。平分时选择较新的观察。
| 信号 | 得分加成 |
|---|---|
| 文件被修改(不仅仅是读取) | +2 |
| 观察涉及3 个或更少总文件数 | +2 |
| 观察涉及4-8总文件数 | +1 |
| 观察涉及9+个文件(概览型) | +0 |
分数决定哪些观察能够入选。保留的观察按从旧到新排列,并按天分组。某条观察中该文件是主要修改对象的,胜过那条只是与 20 个其他文件一起被读取的观察。
Hook 时机¶
位于 Read 上的 PreToolUse hook 是同步的。必须如此,因为只有同步 hook 才能拦截工具调用。每次 Read 都会等待它。
- 该 hook 最多给工作进程 3 秒时间,用于健康检查和观察查询。如果工作进程响应慢或未运行,hook 会停止等待,Read 继续执行且不附加任何内容。门控故障时放行。
- Claude Code 对此 hook 的超时时间为 15 秒,远高于上述预算的安全兜底。
- 如果 hook 因任何其他原因失败,它会返回一个 no-op。错误绝不会阻止 Read。
配置¶
大小阈值¶
- 1,500 字节以下的文件不会获得任何内容:既没有时间线,也不会被拦截。在该大小下,读取文件的成本与时间线大致相当。
- 从 1,500 字节到 32 KB 的文件会获得时间线作为上下文,但绝不会被拦截。
- 32 KB 及以上的代码文件可能被拦截。
拦截并非免费。被拒绝的 Read 算一次轮次,其后的每次 smart_outline、smart_unfold 或定向 Read 也都是一次轮次,而每次轮次都会重新读取整个对话。拦截节省的是未放入上下文的文件。这种节省只有在文件较大时才会超过那些轮次的成本。read-gate 评估(仓库中的 evals/read-gate)在门控开启和关闭两种情况下都提出了相同的走查问题:
| 文件 | claude-sonnet-5-5 |
claude-opus-5-5 |
|---|---|---|
| 513 行,19 KB | -6% | +20% |
| 941 行,49 KB | -35% | -32% |
两个阈值均固定于 src/cli/handlers/file-context.ts 中。
项目排除¶
与 CLAUDE_MEM_EXCLUDED_PROJECTS 中的模式匹配的项目会完全跳过该钩子:不拦截,也不生成时间线。可在 ~/.claude-mem/settings.json 中配置:
如何关闭¶
以下任一方式均可关闭此门:
- 设置文件。 在
~/.claude-mem/settings.json中:
- 查看器。 打开设置(齿轮图标),展开 高级,关闭 阻止全文读取,然后点击 保存。
- 环境变量。 在钩子能读取到的地方设置
CLAUDE_MEM_FILE_READ_GATE_ENABLED=false:在启动 Claude Code 前于 shell 中导出,或将其添加到~/.claude/settings.json的env下。该值会覆盖设置文件。
在设置文件或查看器中做出的更改会在下一次 Read 时生效,无需重启。只有值 false 才会关闭此门;任何其他值都会保持其开启。在 settings.json 中,如上所示,将其写为字符串 "false"。
门关闭后,每次 Read 都会通过,并且该文件的时间线仍会被作为上下文加入,每个文件在每个会话中一次。
要关闭整个钩子(包括时间线),请在钩子环境中设置 CLAUDE_MEM_DISABLE_FILE_CONTEXT=1(参见 关闭工具钩子)。Claude Code 仍会为每次 Read 启动 claude-mem 钩子启动器,但它会在启动 Bun 或 worker bundle 之前退出。
Note
当门关闭时,Claude 会在自行决定时读取整个文件。这在大型文件上会消耗更多 token,但省去了额外的工具调用,对于小型项目或观察数据稀疏时,这可能是一个更划算的取舍。
整体配合方式¶
File Read Gate 是 claude-mem 分层上下文策略的一部分:
- Session Start:注入近期观察的时间线(第 1 层:元数据)
- File Read Gate:用该文件的时间线响应针对代码文件的整文件 Read(第 1 层:元数据)
- get_observations:按需获取特定的观察详情(第 2 层:详情)
- smart_outline / smart_unfold:读取当前代码的结构,或某个符号(第 3 层:源码)
- 带 offset 和 limit 的 Read:读取精确行,例如在 Edit 之前(第 3 层:源码)
每一层的成本都高于前一层。此门让 Claude 从最低成本层开始,仅在需要时升级。在门让位的任何情况下,仍会发生整文件 Read:小文件和非代码文件、自上次观察以来发生更改的文件、子代理、其他主机、未安装 tree-sitter CLI 的安装环境,或门被关闭时。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/claude-mem