跳转至

文件读取门

它是什么

文件读取门会阻止 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 过的文件,而对文件的部分读取即可满足该要求。典型的编辑流程:

  1. Read(file_path):被拦截。Claude 会看到时间线和上面的指示。
  2. smart_outline(file_path):每个符号及其行范围。
  3. Read(file_path, offset=120, limit=40):允许。仅需要修改的函数。
  4. 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 基准测试。


用于时间推理的当前日期/时间

该消息以当前日期和时间开头:

Current: 2026-10-05 3:25pm PDT

这让 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()。

无门控时:

Read (whole file)                           ~22,000 tokens

有门控时:

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_EXCLUDED_PROJECTS": "/tmp/*,/scratch/*"
}

如何关闭

以下任一方式均可关闭此门:

  • 设置文件。 在 ~/.claude-mem/settings.json 中:
{
  "CLAUDE_MEM_FILE_READ_GATE_ENABLED": "false"
}
  • 查看器。 打开设置(齿轮图标),展开 高级,关闭 阻止全文读取,然后点击 保存。
  • 环境变量。 在钩子能读取到的地方设置 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 分层上下文策略的一部分:

  1. Session Start:注入近期观察的时间线(第 1 层:元数据)
  2. File Read Gate:用该文件的时间线响应针对代码文件的整文件 Read(第 1 层:元数据)
  3. get_observations:按需获取特定的观察详情(第 2 层:详情)
  4. smart_outline / smart_unfold:读取当前代码的结构,或某个符号(第 3 层:源码)
  5. 带 offset 和 limit 的 Read:读取精确行,例如在 Edit 之前(第 3 层:源码)

每一层的成本都高于前一层。此门让 Claude 从最低成本层开始,仅在需要时升级。在门让位的任何情况下,仍会发生整文件 Read:小文件和非代码文件、自上次观察以来发生更改的文件、子代理、其他主机、未安装 tree-sitter CLI 的安装环境,或门被关闭时。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/claude-mem