claude-mem 跨平台修复实战:CRLF Shebang 失效、tree-sitter 预检、Chroma 就绪轮询与 .env.local 隔离
本文围绕 claude-mem 仓库中一份真实的 Issue 分诊手册 TRIAGE-06-Cross-Platform-Fixes 展开,系统性讲解四个阻塞 Windows / macOS 用户的中等严重度缺陷及其修复方案:构建产物 CRLF 行尾导致的 shebang 失效(Issue #1342)、tree-sitter CLI 缺失时 smart-explore 工具的静默失败(#1247)、Chroma 初始化竞态导致语义搜索全挂(#1225)、以及项目目录下的 .env.local 污染 Chroma 子进程引发的崩溃(#1297)。读完后你既能掌握每个问题的定位路径与修复手法,也能在仓库源码中找到这些模式已经落地的实现证据与测试用例。
分诊背景:为什么“中等严重度”对受影响用户却是阻塞性的
这份 Phase 06 手册开篇就定义了问题的定位口径:这些缺陷只影响特定平台(主要是 Windows),但会导致 claude-mem 在这些系统上完全无法工作。对整个项目而言是 Medium 级,对受影响用户而言却是 100% 阻塞——因为插件的核心能力(MCP 工具、向量语义搜索)全都依赖这些跨平台敏感的路径。手册给出的执行前提是 Phase 01-05 已完成,涉及四个 Issue:#1342、#1247、#1225、#1297,每个修复都要求“目标明确、风险低”。
这个分诊思路值得借鉴:跨平台缺陷的修复不是统一重写,而是逐项定位到具体的字节级差异(行尾符)、二进制差异(.exe 后缀)或时序差异(进程就绪竞态),然后针对差异本身打最小补丁。
问题一:CRLF 行尾让 shebang 在 macOS/Linux 上失效(#1342)
故障机理
claude-mem 的插件脚本以 .cjs 文件形式分发,例如 plugin/scripts/mcp-server.cjs,其第一行是标准的 Node shebang:
#!/usr/bin/env node
当这个文件被以 Windows 行尾(CRLF,即 \r\n)保存或检出时,实际的第一行字节序列变成 #!/usr/bin/env node\r。操作系统在解释 shebang 时会把 \r 当作可执行文件名的一部分,于是去找一个名为 node\r 的程序——在 macOS/Linux 上自然找不到,脚本整体无法执行。这类故障的典型特征是:文件在 Windows 机器上构建/提交,在 Unix 系机器上以“无权限”或“找不到解释器”的形态炸掉,且用文本编辑器打开完全看不出异常。
手册给出的修复路径
分诊手册要求按以下顺序处理:
- 先确认现状:检查
plugin/scripts/mcp-server.cjs中是否存在\r字符(例如grep -qU $'\r'逐个扫描目录); - 在构建环节修,而不是只修文件——文件本身转成 LF 只是治标,构建脚本每次重新生成产物时可能再次引入 CRLF;
- 两条可选的根治路径:
- 在生成
plugin/scripts/*.cjs的构建步骤里,写入前追加.replace(/\r\n/g, '\n'); - 或者增加
.gitattributes条目:plugin/scripts/*.cjs text eol=lf,从检出层面强制 LF;
- 在生成
- 一次修全:
plugin/scripts/下所有.cjs文件都要检查,不能只修报错的那一个; - 验收标准:构建后读取每个生成脚本的第一行,确认 shebang 干净。
仓库中的实现证据
当前仓库已经具备手册建议的 .gitattributes 方案,.gitattributes 的内容明确写着:
* text=auto eol=lf
plugin/scripts/*.cjs eol=lf
plugin/scripts/*.js eol=lf
*.png binary
*.jpg binary
...
全局 eol=lf 加上对 plugin/scripts/ 的显式约束,意味着无论贡献者本机是 Windows 还是 macOS,Git 检出时都会把这些脚本规范化为 LF,直接消除了 shebang 被 \r 污染的路径。同时,package.json 中 build 脚本串起了 node scripts/sync-plugin-manifests.js && node scripts/build-hooks.js && node scripts/gen-plugin-lockfile.cjs,这正是手册所说“所有生成 plugin/scripts/*.cjs 的构建入口”——按手册要求,这些生成器写出文件前都应保证内容是 LF(.replace(/\r\n/g, '\n') 是构建写入步骤中最常见的落点)。
一个实操层面的提醒:如果你的工作树里这些文件已经带了 CRLF,光有 .gitattributes 不会自动改写磁盘内容,需要重新检出(例如 git checkout -- plugin/scripts/ 或重新克隆)让 Git 按 eol=lf 重新落盘。验证方式与手册一致:读取每个 .cjs 第一行,shebang 应该恰好是 #!/usr/bin/env node 而不再有多余字符。
问题二:Windows 上 tree-sitter CLI 缺失导致 smart-explore 静默失败(#1247)
故障机理
claude-mem 的 smart-explore 能力由三个 MCP 工具组成:smart_search、smart_outline、smart_unfold,其工具定义可见 src/servers/mcp-server.ts(第 669、706、750 行附近)。这些工具的底层解析器是 src/services/smart-file-read/parser.ts,它通过 execFileSync 直接调用 tree-sitter 命令行工具执行 S-CAP 查询:
const execArgs = ["query", "-p", grammarPath, queryFile, ...sourceFiles];
output = execFileSync(bin, execArgs, {
encoding: "utf-8",
timeout: 30000,
stdio: ["pipe", "pipe", "pipe"],
});
问题在于:tree-sitter CLI 需要 C 编译器才能从其 npm 包构建本地二进制,而多数 Windows 用户机器上没有 MSVC/MinGW,CLI 安装失败或根本不存在。原实现的故障表现是静默失败——查询执行抛错后被 catch 吞掉、只写了一行 debug 日志,MCP 工具返回空结果,Agent 和用户都察觉不到“搜索坏了”,只会觉得“为什么没搜到东西”。
手册给出的修复方案
- 预检(pre-flight check):在真正调用 tree-sitter 之前,先跑一次
tree-sitter --version(或等价的轻量探测)确认可用性; - 优雅降级而非抛异常:若预检失败,MCP 工具应把一条指导性错误作为 content 返回,而不是让调用链 throw。手册给出的文案是:
tree-sitter CLI not available. On Windows, install via 'npm install -g tree-sitter-cli' or use WSL. - 明确不做自动安装:只给出清晰指引,不替用户装依赖——这是“低风险修复”的分诊约束。
仓库中的实现证据:二进制解析与平台后缀
比“是否可用”更早一层的问题是在 Windows 上二进制叫什么名字。tree-sitter-cli 在 Windows 上安装的是 tree-sitter.exe,而不是裸的 tree-sitter;如果查找逻辑只拼 POSIX 名字,existsSync 永远查不中,代码会静默回退到 PATH 上可能不存在的裸命令——这正是 parser.ts 源码注释里复盘的故障(参见 parser.ts 第 355-374 行):
// tree-sitter-cli installs `tree-sitter.exe` on Windows, not a bare `tree-sitter`
// Without the `.exe` suffix the existsSync check below always misses on Windows,
// silently falling through to a bare `tree-sitter` that may not be on PATH —
// smart file parsing then returns empty results with no error.
export function resolveTreeSitterBinPath(platform: NodeJS.Platform = process.platform): string {
const binName = platform === "win32" ? "tree-sitter.exe" : "tree-sitter";
try {
const pkgPath = _require.resolve("tree-sitter-cli/package.json");
const binPath = join(dirname(pkgPath), binName);
if (existsSync(binPath)) {
return binPath;
}
} catch {
// tree-sitter-cli not in node_modules is expected; falls back to PATH
}
return binName;
}
这段代码体现了完整的修复链路:优先解析 node_modules/tree-sitter-cli 包目录下的真实二进制(按平台选 .exe 后缀),解析不到才回退 PATH。而对应测试 tests/services/smart-file-read/tree-sitter-bin-windows.test.ts 用 bun:test 锁死了两条契约:win32 平台必须解析出以 tree-sitter.exe 结尾的路径;linux/darwin 平台绝不允许出现 .exe。这比单纯“加一个预检”更彻底——查找逻辑本身不再依赖 PATH 命中。
另外值得注意的是 parser.ts 的查询失败分支(第 409-415 行):失败时记录 debug 级日志并返回空 Map,配合 MCP 层把“不可用”转化为可读 content 的策略,整体保持了“工具永不 throw、降级信息随结果返回”的纪律,与手册第 24 行的要求一致。
问题三:Windows 上 Chroma 初始化竞态导致语义搜索全挂(#1225)
故障机理
chroma-mcp 是以 Python 子进程形式运行的 MCP 服务。故障时序是:Node 侧执行 client.connect() 成功后立即发起业务请求,但 Python 解释器冷启动(Windows 上尤其慢)还没完成初始化,于是 chroma-mcp 返回 "Received request before initialization was complete",之后所有语义搜索请求连锁失败。
手册给出的修复方案
- 阅读 src/services/sync/ChromaMcpManager.ts,定位客户端连接与初始化命令序列、以及“initialization”状态跟踪点;
- 在
connect()之后加入就绪轮询:以一次轻量 Chroma 操作(手册点名chroma_list_collections)在重试循环里探测,最多 5 次、间隔 2 秒,全部通过后才标记为 connected; - 如果已有就绪轮询,则针对 Windows 的 Python 慢启动调大超时或重试次数;
- 同时评估 Phase 02 引入的
CLAUDE_MEM_CHROMA_STARTUP_DELAY_MS设置是否有效,若有效则提高 Windows 下的默认值。
仓库中的实现证据
当前版本的 ChromaMcpManager.ts(共 1520 行)已经包含手册要求的“用真实业务调用做就绪探测”这一核心手法——第 874 行出现了 await this.callTool('chroma_list_collections', { limit: 1 }),即以最小 limit 的集合列表查询作为轻量心跳;第 894 行则是正式拉取集合(limit: 100)。从源码结构看,这个 limit: 1 的调用就是初始化后/连接恢复时的就绪验证点,语义与手册描述的“readiness poll”一致。
另一个值得注意的配套细节是:该文件的子进程环境并非裸继承,而是经过了 stripForeignPythonEnv(清除宿主环境里可能干扰 Python 工具链的变量)、sanitizeEnv 以及 supervisor 的进程登记(captureProcessStartToken、isPidAlive)。这说明 Chroma 子进程在“启动”这条线上已经被当成一个需要环境隔离 + 身份校验 + 生命周期托管的一等公民,与问题四的修复共同构成了完整的子进程卫生规范。
问题四:macOS 上项目 .env.local 污染 Chroma 子进程导致崩溃(#1297)
故障机理
chroma-mcp 内部使用 pydantic Settings,后者会自动读取当前工作目录下的 .env / .env.local 文件。claude-mem 启动 chroma-mcp 子进程时若继承了用户项目目录作为 CWD,那么项目里任何与 Chroma 配置项同名的环境变量(比如 CHROMA_*、SERVER_HOST 之类)都会被 pydantic 当成 Chroma 自身配置解析,轻则行为异常,重则直接崩溃。这个故障只在“项目恰好有 .env.local”时出现,属于典型的环境依赖型偶发崩溃。
手册给出的修复方案(两条路径)
- 环境变量阻断:在 spawn 选项的
env中追加PYDANTIC_SETTINGS_DOTENV_PATH=''或DOTENV_PATH='',让 pydantic 不再读取项目的 dotenv 文件; - CWD 隔离(手册标注的替代方案,通常更彻底):把 chroma-mcp 子进程的工作目录显式设置为安全目录(手册举例
~/.claude-mem/),而不是继承项目 CWD。关键是“搜索代码确认 CWD 是否继承自父进程,若是则显式覆盖”。
仓库中的实现证据:CWD 已改为用户主目录
对照当前 ChromaMcpManager.ts 源码,两条 spawn 点(约第 201、240 行与第 659 行附近)都显式设置了 cwd: os.homedir(),即子进程工作目录被固定到用户主目录而非项目目录——这正是手册“Alternative”路径的落地:从根上切断了 pydantic 读到项目 .env.local 的可能,而不必依赖逐个环境变量置空(后者容易漏,前者一次覆盖所有 dotenv 来源)。结合前文提到的 stripForeignPythonEnv 与 sanitizeEnv,整个子进程环境链是:宿主环境 → 剥离外来 Python 变量 → supervisor 白名单净化 → 显式 CWD → uvx 启动,任何一层引入的脏配置都到不了 Chroma。
验证与回归:构建链路和测试门禁
手册最后的收尾任务给出了明确的验收标准:
npm test全绿(在本仓库即 package.json 中的"test": "bun test tests",测试目录覆盖tests/services/smart-file-read/、tests/infrastructure/等跨平台敏感面);npm run build-and-sync通过,对应脚本"build-and-sync": "npm run build && npm run sync-marketplace && node scripts/restart-marketplace-worker.cjs",其中build正是前面 CRLF 问题要求把关的.cjs生成链路;- macOS 手工验证:在一个包含
.env.local的项目里确认 chroma-mcp 能干净启动——这是问题四的端到端验收,无法被单测完全替代。
从仓库测试资产看,跨平台修复的回归防线是成体系的:tests/services/smart-file-read/tree-sitter-bin-windows.test.ts 锁死 Windows 二进制解析契约;tests/integration/chroma-windows-lifecycle.test.ts、tests/integration/chroma-vector-sync.test.ts 覆盖 Chroma 生命周期与向量同步;tests/infrastructure/ 下还有 windows-hide-regressions.test.ts、worker-wrapper-windows-hide.test.ts 等 Windows 专属回归。手册中四个 Issue 的每一个修复,都应当对应至少一个这样的“失败即红”的测试,而不是只靠手工验证。
小结:跨平台修复的四条可复用原则
把这份 TRIAGE-06 手册的四个任务抽象开,其实沉淀了四条在 claude-mem 这类多平台 Agent 插件中反复适用的工程原则:
- 字节级差异要在生成链路修:行尾符、BOM、编码这类问题,修文件是治标,修构建写入步骤 +
.gitattributes才是治本; - 可选依赖必须预检并降级为可读结果:tree-sitter 这类需要本机编译器的工具在部分平台天然缺位,工具层应把“不可用”作为 content 返回并给出安装指引,绝不静默吞错、也绝不替用户自动安装;
- 外部进程连接成功 ≠ 就绪:对 Python/MCP 子进程,
connect()之后必须用真实轻量调用做就绪轮询,且要为慢启动平台(Windows + Python 冷启动)预留更宽松的重试预算; - 子进程环境默认不可信:显式
cwd、白名单化env、剥离宿主环境干扰变量,三者共同保证外部库的“自动读取约定”(如 pydantic 的 dotenv 搜索)不会把用户项目文件当成自己的配置。
对照仓库现状,这四个方向在 src/services/smart-file-read/parser.ts、src/services/sync/ChromaMcpManager.ts 与 .gitattributes 中都已能找到落地实现与对应测试,可以作为同类多平台 Agent 工具做跨平台加固时的直接参考样本。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00