Skip to content

fix(cli): 同一时间戳刻度内新增的目录项不再被缓存漏掉;插件安装测试改用可执行临时目录 - #85

Merged
qiansc merged 1 commit into
mainfrom
fix/env-dependent-tests
Oct 26, 2026
Merged

qiansc merged 1 commit into
mainfrom
fix/env-dependent-tests

Conversation

@qiansc

@qiansc qiansc commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Summary

修复长期存在的 4 个测试失败。其中 2 个暴露的是真实缺陷:同一命令内,目录在同一个文件系统时间戳刻度内新增文件时,后续读取看不到新文件。另外 2 个是测试在 noexec 的系统临时目录里执行假的宿主 CLI。不改版本号,随下一个版本发布。

Compatibility

无数据或接口兼容影响。目录列表在目录最近 1 秒内有改动时不再复用缓存(重新 readdir),其余文件读取的复用行为不变。

Verification

见下方「自测情况」。

Checklist

  • bun run verify:full passes.(本地跑了 lint 与 context-cli 全量 bun test,见自测情况)
  • Lifecycle changes include route and recovery tests.(不涉及)
  • Published package metadata and local pack behavior were checked when affected.(不涉及)
  • English and Simplified Chinese user documentation remain behaviorally equivalent.(未改文档)
  • Runtime code remains compatible with Node.js 20+.
  • No generated output, secrets, private sources, or local workspaces are included.

背景

在开发环境中,以下 4 个测试在未修改的 main 上稳定失败:

  • statusReadCost:command reads share bytes but detect additions…
  • productionReadReuse:shared directory reads observe nested additions…(时好时坏)
  • cli:plugin install continues available agents when codex is missing
  • cli:plugin install replaces an existing Claude plugin…

目录缓存(真实缺陷):命令内目录列表以目录的 dev/ino/size/mtime/ctime 作为缓存戳。文件系统按粗粒度刻度推进时间戳(ext4 按内核 tick),同一刻度内新增文件不会改变任何一项;ext4 目录大小也保持 4096。实测在 ext4 上连续新增文件,200 次中有 195 次目录戳完全不变,因此同一命令里随后的读取会复用旧列表,漏掉新文件。CI 通过只是因为时序不同。

插件安装测试(测试环境假设):测试把假的 claude/codex 写到 os.tmpdir() 并放进 PATH;系统临时目录挂载为 noexec 时启动失败(EACCES: posix_spawn 'claude')。

改动

  • commandReadCache.ts:reuseCommandFileRead 新增 racyWindow 选项,目录列表(readCommandDirectory)启用。与 Git 的 racily-clean 规则相同:路径的 mtime 在读取前 1 秒内时,该次读取不被复用。文件读取不启用:Context 以原子替换写文件(新 inode),已有戳足以发现修改,同一命令内对刚写入文件的复用与并发合并保持不变。
  • statusReadCost.test.ts:新增回归用例,在同一命令内连续新增 50 个文件并逐次断言列表长度。
  • cli.test.ts:新增 executableTmpdir(),先在系统临时目录试运行一个探测脚本,不可执行时改用包内已忽略的 .tmp/cli-exec-tests;三个放置假 CLI 的插件安装测试改用它。

风险

目录在最近 1 秒内有改动时,同一命令内会多做几次 readdir;只影响刚被修改的目录,开销很小。

向后兼容

无行为兼容影响;CLI 输出与 JSON 不变。

自测情况

  • 修复后:statusReadCost、productionReadReuse、commandReadCache、commandReadConsumers、projectWriteLockV0618 全部通过(连续 3 次);cli.test.ts 29 pass。
  • 去掉修复只保留测试:新回归用例与原有两条目录新增用例失败,确认用例能复现缺陷。
  • context-cli 全量 bun test:1365 pass,1 fail。失败的是负载下的超时用例(projectKnowledgeUpdate,5 秒超时),单独运行 3 次均 17/17 通过,与本改动无关。
  • lint 0 error;typecheck 通过。

🤖 Generated with Claude Code

…an executable temp dir

Command-local directory listings were keyed on the directory's dev, inode,
size, mtime and ctime. File systems advance timestamps in coarse ticks
(ext4 by kernel tick), so adding an entry in the same tick as an earlier
change left the stamp equal and a later read in the same command missed
the new file. Like Git's racily-clean rule, a listing whose directory
changed within the last second is now never reused. File reads keep their
reuse: Context replaces files atomically, so a change gets a new inode.

Plugin install tests put fake claude/codex executables on PATH under the
system temp directory, which hardened hosts mount noexec. They now probe
once and fall back to the ignored package-local .tmp/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@qiansc
qiansc merged commit 19baadf into main Oct 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant