ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

claude-mem Windows 进程树治理:Chroma 子进程链的完整回收与孤儿进程防线

claude-mem Windows 进程树治理:Chroma 子进程链的完整回收与孤儿进程防线 claude-mem Windows 进程树治理Chroma 子进程链的完整回收与孤儿进程防线【免费下载链接】claude-memPersistent Context Across Sessions for Every Agent – Captures everything your agent does during sessions, compresses it with AI, and injects relevant context back into future sessions. Works with Claude Code, OpenClaw, Codex, Gemini, Hermes, Copilot, OpenCode More项目地址: https://gitcode.com/GitHub_Trending/cl/claude-mem本文基于计划文档 plans/2026-08-18-chroma-windows.md 展开解析 claude-mem 在 Windows 上治理 Chroma 语义同步子进程链worker → uvx.exe → uv.exe → python.exe → chroma-mcp的完整方案为什么单 PID 的process.kill会制造端口 wedged、144GB 临时目录泄漏与静默同步失败三大事故如何通过共享的killProcessTree树级回收、uvx 子进程环境净化和 Windows CI 回归门把「进程树归属权」变成可验证的工程不变量。读完你将掌握跨平台进程树 teardown 的算法细节、PID 复用的身份校验机制以及让 CI 真正证明 Windows 行为的测试设计。问题本质Windows 没有 POSIX 进程组单 PID kill 必然留下孤儿claude-mem 的 Chroma 语义同步通过uvx直接拉起chroma-mcp早期 cmd.exe 参数改写问题 #2954/#3121 已修复ChromaMcpManager不再使用 shell 包装。但 Windows 上这条链是 4 层深度的原生进程链worker → uvx.exe → uv.exe → python.exe → chroma-mcpWindows 没有 POSIX 进程组而 Node 的process.kill(pid, SIGTERM)只会强制终止恰好一个PID。计划文档审计发现除个别路径外所有 teardown 点都按单 PID kill于是子孙进程全部存活。存活者造成三个标志性 Windows 事故存活效应Issue症状孤儿继承 worker 的监听 socket#3482端口 37777 卡死、834 次健康检查失败、hooks 被硬阻塞uv在构建中途被杀临时目录永不回收#3540builds-v0/.tmp*泄漏 —— 实测 144.21 GB / 696 个目录外部VIRTUAL_ENV被继承进 uvx 沙箱#3552numpy ABI 冲突语义同步静默停止正确的修复在仓库里其实已经存在但当时没有被复用ChromaMcpManager.killProcessTree()原src/services/sync/ChromaMcpManager.ts:1039-1154实现了 POSIX 后代遍历 Windowstaskkill /PID n /T /F其他任何路径都不使用它。整份计划要解决的核心矛盾就是把这套经过实战验证的实现从 Chroma 私有变成全仓库共享并把每一个 Windows kill 站点全部改走它。允许使用的 API对照源码核实不得杜撰计划明确划定了修复的 API 边界taskkill /PID n /T /F经execFileAsync调用—— Windows 树级 kill 的唯一正解先例即ChromaMcpManager.ts:1044-1047pgrep -P pid—— POSIX 后代遍历先例即ChromaMcpManager.ts:1124-1154process.platform win32—— 全仓库统一的平台检测惯用法path.join/pathToFileURL/fileURLToPathgetSupervisor().registerProcess/unregisterProcesssrc/supervisor/index.tsChroma 已以chroma-mcp身份注册。同时文档列出了反模式清单这些能力不存在于 Node 标准能力中不得触碰Windows Job Objects —— Node 没有暴露 Job Object API引入需要原生插件或 FFI超出范围在 Windows 上期待process.kill(pid, SIGTERM)实现优雅退出 —— 它始终是硬终止任何新的 npm 依赖 —— 该 PR 必须零依赖shell: true或用cmd.exe包装uvx—— 会重新引入 #2954/#3121。Phase 1提取共享树级回收模块 kill-process-tree计划的第一阶段是纯提取把ChromaMcpManager内部可用的实现原样搬入共享模块导出killProcessTree(pid, opts?)与collectDescendantPids(pid)不重设计算法让 diff 可审。当前仓库中该模块已落地为 src/shared/kill-process-tree.ts其文件头注释明确交代了动机Windows has no process groups, and Nodesprocess.kill(pid, signal)force-terminates exactly one PID. Any spawn chain deeper than one level (uvx - uv - python - chroma-mcp, or a.cmdshim wrapping a real binary) leaves descendants running — they inherit listening sockets and wedge the worker port.Windows 没有进程组Node 的单信号 kill 只终止一个 PID。任何超过一层的 spawn 链——uvx - uv - python - chroma-mcp或包裹真实二进制的.cmdshim——都会留下存活子孙它们继承监听 socket 并卡死 worker 端口。算法本体POSIX 叶先根后Windows 一把 taskkillkillProcessTree的核心行为见 kill-process-tree.tsWindows 分支execFileAsync(taskkill, [/PID, String(pid), /T, /F])5 秒超时。退出码 128 或 stderr 匹配/not found|no running instance|no tasks/i表示「目标已不存在」是预期的容忍情形而非错误其余任何失败访问被拒、超时、/T遍历卡死都会抛出ProcessTreeKillError—— 因为调用方如server stop绝不能在一个实际未完成的 kill 上报告成功。POSIX 分支先用pgrep -P递归收集全量后代集pkill -P只够到直接子进程uv下的python/chroma-mcp孙进程会重新挂到 init 名下存活然后先信号叶子、再信号根graceful 模式下 SIGTERM 后等待 500ms 沉降窗口再对「SIGTERM 前快照 沉降后重扫」两个后代集的并集发送 SIGKILL —— 因为根退出后子进程会重新父化任何单一快照都会漏。两种信号模式graceful 与 immediateexport interface KillProcessTreeOptions { signalMode?: graceful | immediate; // 默认 graceful expectedStartToken?: string | null; }graceful默认POSIX 走 SIGTERM → 500ms 沉降 → SIGKILLimmediatePOSIX 全程 SIGKILL、无 SIGTERM 无沉降。这是陈旧 worker 版本回收#3378的硬性要求—— 该路径必须运行零个陈旧版本的 shutdown 代码。SIGTERM 是可捕获的若陈旧 worker 捕获它就会执行正在被卸载安装版的 shutdown/handoff 逻辑恰好触发该不变量要防止的重启风暴SIGKILL 不可捕获树级 SIGKILL 回收全部子孙的同时让陈旧代码一行都跑不起来。在 src/shared/worker-utils.ts 中可以看到这正是 #3482 的直接修复点// immediate is required, not incidental: it sends SIGKILL with no // SIGTERM and no grace window ... SIGKILL is uncatchable, so tree-SIGKILL // reaps descendants without ever letting stale code run. await killProcessTree(stalePidInfo.pid, { signalMode: immediate });Windows 上两种模式无差异taskkill /T /F在那里无条件立即生效。PID 复用防线start token 身份校验一个裸 PID 不构成 kill 授权进程退出后 OS 可能立刻把该号码重新发给无关进程。模块通过 start tokenLinux 的/proc/pid/statstarttime 字段、macOS 的ps -o lstart、Windows 的Win32_Process.CreationDate格式化字符串格式与各平台captureProcessStartToken()严格对齐解决expectedStartToken可选且缺省安全未提供时函数在入口自行捕获根 token 并在每次信号前复检因此「根身份校验」是默认行为而非调用方可遗忘的参数调用方预先捕获则能额外检出进入函数之前的复用如ChromaMcpManager在await transport.close()之前捕获的 PIDcollectDescendantIdentities()一次性读取整个进程表Linux 读/proc、macOS 一次ps快照、Windows 一次 CIM 查询发现与身份取自同一次观测—— 先枚举 PID 再逐个探测 token 会更糟号码被复用时会捕获「继任者」的 token复检反而把陌生人认证为合法目标身份不符时函数什么都不做既不信号根也不从它枚举子孙那会是继任者的孩子Windows 上taskkill /T会连整棵陌生子树一起拉倒。collectDescendantPids()则是只读 PID 视图供「只枚举、不发信号」的调用方使用。这套机制有专门的测试矩阵tests/shared/kill-process-tree-cross-platform.test.ts、tests/shared/kill-process-tree-identity.test.ts、tests/shared/kill-process-tree-modes.test.ts、tests/shared/kill-process-tree-pid-reuse.test.ts。计划阶段的验证要求是grep -n killProcessTree src/services/sync/ChromaMcpManager.ts只剩 import 没有本地定义npm run build干净macOS 上 Chroma teardown 行为不变。当前仓库中killProcessTree的引用方正是计划 Phase 2 点名的那些文件ChromaMcpManager.ts、process-registry.ts、shutdown.ts、worker-utils.ts、ServerService.ts。Phase 2让所有 Windows kill 站点改走共享 helper计划审计出了 7 个单 PID kill 站点并要求逐个替换为killProcessTree文件:行原代码在 Windows 上为何失效src/supervisor/process-registry.ts:319process.kill(record.pid, SIGTERM)cmd.exe 包装器死了Claude 子进程成孤儿src/supervisor/process-registry.ts:353process.kill(record.pid, SIGKILL)收割器只杀 1 个 PID随后仍删除注册表src/supervisor/process-registry.ts:482proc.kill(SIGKILL)只杀.cmd包装器src/supervisor/process-registry.ts:770process.kill(record.pid, SIGTERM)重复 SDK 清理让子孙成孤儿src/supervisor/shutdown.ts:186process.kill(pid, signal)根进程退出survivor 扫描永远到不了 taskkill 分支src/shared/worker-utils.ts:514process.kill(stalePidInfo.pid, SIGKILL)价值最高—— 版本回收让整个 uvx→chroma 链持着 socket 存活src/server/runtime/ServerService.ts:363process.kill(existing.pid, SIGTERM)跳过 DB/queue/HTTP 清理处理器其中worker-utils.ts:514是 #3482 的直接成因计划特别强调即使整个阶段被裁剪这一处也必须修。验证标准同样苛刻grep -rn process\.kill( src/ | grep -v kill-process-tree.ts的每一个剩余命中都必须是 POSIX-only 或有意的并在 PR 正文中逐一说明理由。Chroma 自身的 teardown 路径还叠加了一层「双快照」孤儿回收逻辑ChromaMcpManager.tsclose 之前拍一次后代快照、close 之后再扫一次并与前者求并集—— 因为根退出后子孙重新父化第一次遍历若跑在 uvx fork 出uv之前就会是空的单靠任一次快照都会漏两次采样点都不可见的进程才算真正逃逸。重杀前还逐个复检 start tokenreapOrphanedDescendantsChromaMcpManager.tstoken 不匹配说明 PID 已被回收跳过而不是误杀。Phase 3优雅优先堵住 uv builds-v0 临时目录泄漏uv在构建中途被树杀正是 #3540 的根因。计划要求 Chroma teardown 路径先尝试优雅退出只在宽限期后升级到taskkill /T /F并复用既有的 500ms 沉降惯用法而非发明新定时器。当前源码中的实现与计划完全一致ChromaMcpManager.ts// #3540 — graceful FIRST, hard tree-kill only as escalation. // The previous order tree-killed before closing, so uv was always // SIGKILLed mid-build and never unlinked its builds-v0/.tmp* scratch dir. // StdioClientTransport.close() already implements exactly the escalation // this needs — stdin EOF, wait 2s, SIGTERM, wait 2s, SIGKILL — so the // grace period is the SDKs, not a new timer scheme of ours.注释还点明了 close/exit 竞态的两个方向必须同时处理升级太急uv在构建中途被 SIGKILL 就是 #3540升级太慢整条链成孤儿就是 #3482。由于close()可能在 Node 处理完 exit 事件前就 resolve读exitCode需要有界等待waitForChildExit而非瞬时读取。Windows 上升级是无条件的close()在 Windows 下等价于对单个 PID 的TerminateProcess读 exitCode 会跳过唯一能触及uv → python → chroma-mcp的taskkill /T /F。计划同时要求在 Chroma启动时而非关闭时关闭可能是硬杀对 uv 缓存的builds-v0目录做一次陈旧临时目录清扫限定为超过保守阈龄的条目定位 uv 目录必须读 src/shared/uvx-bin-dirs.ts支持CLAUDE_MEM_CHROMA_UVX_PATH覆盖、~/.local/bin、~/.cargo/bin、Homebrew 目录等平台规则不得硬编码路径。硬性守卫目录不存在时清扫是 no-op全新安装不能崩清扫绝不删除属于存活uvPID 的目录不递归删除已解析 uv 缓存目录之外的任何东西 —— unlink 前必须断言解析路径位于 uv 缓存根之下。Phase 4净化 uvx 子进程环境斩断外部 Python 继承uvx --python 3.13自建临时环境但 CPython 仍会尊重继承来的解释器变量。当 worker 从激活的 venv 或 conda shell 中启动时chroma-mcp 子进程会继承外部 prefix把外层解释器的 site-packages 叠加到 uv 之上典型结果是numpy.dtype size changed的 ABI 冲突 —— 且由于失败发生在 MCP 握手完成之前的子进程里症状就是语义同步静默停止#3552。计划要求在子进程 env 构建的唯一点剔除 5 个变量VIRTUAL_ENV、PYTHONHOME、PYTHONPATH、CONDA_PREFIX、CONDA_DEFAULT_ENV。当时存在两份平行实现ChromaMcpManager.getUvxPreflightEnv与src/services/worker/dependency-preflight.ts:75的effectiveUvxEnv计划明确「两处都净化或统一DRY —— 优先统一」。当前仓库选择了统一src/shared/uvx-env.ts 导出唯一的FOREIGN_PYTHON_ENV_VARS规则与stripForeignPythonEnv(env, platform?)两个构建点都改为调用它ChromaMcpManager.ts、dependency-preflight.ts。其中有一个容易被忽略的 Windows 细节被完整保留Windows 环境变量名大小写不敏感一个导出了PythonPath...的 shell 会产生 CPython 完全认账的变量而按精确大写删除会漏掉它因此 win32 分支对所有大小写变体一律剔除这也保证PYTHONPATH与PythonPath这样的重复变体不会双双传入子进程对 OS 而言它们是同一个变量同时传是未定义行为。platform参数可注入使该规则在 Windows 之外也能被测试覆盖。计划的验收标准是一条单元测试给定被污染的process.env断言构建出的 env 对象不含这 5 个键。Phase 5让 Windows CI 真正证明修复本计划的交付物计划直言这是这些 bug 反复出货的根因原.github/workflows/windows.yml是纯构建作业runs-on: windows-2022步骤只有 install → build → 一个 Bun resolver 测试从不 spawn Chroma而开发团队在 macOS 上无法本地测 Windows。计划要求的 Windows 作业在 runner 上安装 uvPowerShell 安装器与setup-runtime.ts:200一致走生产代码路径拉起真实的 chroma-mcp单文档往返建 collection → add → query → 断言结果返回关闭 worker断言零孤儿不残留任何chroma-mcp、uv.exe、python.exe后代用tasklist/Get-CimInstance Win32_Process按父链过滤断言无临时泄漏builds-v0/.tmp*计数没有增长。第 5、6 步是回归门是全部意义所在。验收标准极其明确该作业必须在main上失败证明它真的抓得住 bug在应用 Phase 1-4 后通过如果它在main上就通过说明测试写错了。当前仓库中该作业已落地为 windows.yml 的chroma-windowsjobchroma lifecycle · worker-recycle orphan gate其注释原样保留了动机# The reason Windows Chroma bugs kept shipping: the build job above never # spawns Chroma, so #3482 (worker recycle orphans the uvx - uv - python # ... # THE regression gate. Fails on main (single-PID SIGKILL orphans the # chroma chain), passes once the recycle path tree-kills on Windows. - name: Worker-recycle orphan gate (#3482) run: $bun test tests/integration/worker-recycle-orphans.test.ts --timeout 600000对应测试即 tests/integration/chroma-windows-lifecycle.test.ts含「hostile Python env」 hostile 环境用例对应 Phase 4与 tests/integration/worker-recycle-orphans.test.ts辅助工具在 tests/integration/helpers/process-tree.ts。值得注意的是 POSIX 侧同样设了同名回归门ci.yml 中的chroma-recycle-gatejob 注明「stale worker 在 POSIX 上也会孤儿化同样的 uvx → uv → python 链」同一套 orphan gate 测试在两个平台各自把关。作业里还把超时从生产默认 120s 放宽因为冷启动的 uvx resolve chromadb 构建远超生产窗口。Phase 6PR 组织与重叠披露计划对交付物本身也有工程纪律要求分支fix/chroma-windows-process-tree从当前main切出PR 正文必须写明根因、7 个 kill 站点、CI 证明main 失败 / 修复后通过、与哪些社区 PR 重叠重叠披露当时经gh核实ChromaMcpManager.ts被 4 个开放 PR 同时编辑 —— #3541uv 宽限期、#3567环境净化、#3286Job Object、#3292宽泛恢复彼此互不引用。本 PR 用共享 helper 覆盖 #3541 与 #3567 的地盘替代四份独立改动并在 PR 正文中显式致谢相关开放项还有 #3309socket 继承当时处于 CONFLICTING/DIRTY 状态、#3416端口重绑、#3529windowsHide、#3321where.exe PATH。Babysit盯 CI、处理 review 意见、重跑直至 green 且可合并。注意 #3286 走 Job Object 路线与本计划明确划定的反模式一致地被排除 —— Node 标准 API 不暴露 Job Object共享 helper 的taskkill /T /F是零依赖正解。范围外事项PR 中须明示不得悄悄丢弃原生 Windows 上的 Bash-only hooksplugin/hooks/hooks.json 的shell: bash—— 归属 plan-master #3605tree-sitter.exe查找失败src/services/smart-file-read/parser.ts:362—— 真实的 MAJOR bug独立 PR~\波浪号展开src/shared/paths.ts:32—— 真实的 MAJOR bug独立 PR大小写敏感的路径包含检查P5、P6/dev/nullvsNULP3、P4—— MINOR对齐其余 31 个开放的 Windows/Chroma 相关 PR。如何在仓库中验证本方案关注点入口共享树级回收实现src/shared/kill-process-tree.tsPID 身份/start tokensrc/shared/process-identity.tsuvx 环境净化规则src/shared/uvx-env.tsuv 二进制/缓存目录定位src/shared/uvx-bin-dirs.ts版本回收 immediate 树杀src/shared/worker-utils.tsChroma 优雅优先 teardown 双快照孤儿回收src/services/sync/ChromaMcpManager.tsuvx 依赖预检环境src/services/worker/dependency-preflight.ts单元/集成测试tests/shared/ 下 4 个 kill-process-tree 测试、tests/integration/chroma-windows-lifecycle.test.ts、tests/integration/worker-recycle-orphans.test.tsCI 回归门windows.yml 的chroma-windowsjob、ci.yml 的chroma-recycle-gatejob这套方案的可迁移经验很清晰在无进程组的平台上做跨平台子进程链治理(1) 树级 kill 必须收敛到单一共享实现而非每处手写(2) kill 授权必须绑定比 PID 更强的身份证据start token且发现与身份取自同一次进程表观测(3) 优雅退出与强制升级之间要有有界竞态处理因为「升级太急泄漏临时产物、升级太慢制造孤儿」是两个方向相反的真实事故(4) 无法本地复现的平台Windows其回归证明只能来自 CI且该 CI 作业必须先被证明在修复前会失败。【免费下载链接】claude-memPersistent Context Across Sessions for Every Agent – Captures everything your agent does during sessions, compresses it with AI, and injects relevant context back into future sessions. Works with Claude Code, OpenClaw, Codex, Gemini, Hermes, Copilot, OpenCode More项目地址: https://gitcode.com/GitHub_Trending/cl/claude-mem创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表