Conversation
- zcode_running 复用 zcode_patcher 跨平台检测:原实现直接跑 tasklist(Windows 专属), macOS/Linux 上 FileNotFoundError 未捕获,看护第一次轮询即崩,且 stderr 被 DEVNULL 吞掉,表现为「等退出 0 次、完成 0 次」,重打包级补丁永远写不进去 - resolve_install 新增 _find_exe:macOS 取 Contents/MacOS/ZCode,Linux 尝试小写命名 - 重启命令按平台分流:Windows creationflags / macOS open <bundle> / 其他 start_new_session - sync.start_watchdog POSIX 加 start_new_session=True(否则看护留在钩子进程组, 应用退出时被整组信号连带杀掉) - __main__ 加 try/except 写日志,崩溃不再无声 - 全部 Popen 补齐 no_window_kwargs()/no-window-ok 标注
- _AsarWriteLock 跨进程文件锁原来无条件 import msvcrt(注释误以为只在 Windows
注入客户端),macOS/Linux 上 4 项重打包级补丁全部 ModuleNotFoundError 失败;
改为 Windows msvcrt.locking / POSIX fcntl.flock 分流
- _from_running_processes 存 PureWindowsPath:.exe 行是 Windows 路径,POSIX 上
Path("C:...") 抛 NotImplementedError;discover() 消费时 map(Path, roots) 转回
_archive_backup 把 bak 与 bak.meta.json 一起改名成 stale-*,两者都命中 glob 模式;目录枚举顺序随时间戳哈希变化,stale[0] 取到谁是随机的—— 实测同代码 10 跑 4~6 挂。排除 .meta.json 后 20 连跑全绿。
macOS 实测:ZCode 每次退出都会泄漏一个 chrome_crashpad_handler(ppid=1, 命令行仍含 ZCode.app 路径),pgrep -f ZCode 因此永远非零——用户明明已 完全退出,看护却判定「仍在运行」,重打包级补丁永远写不进去。本机曾堆积 3.9.1 / 3.10.1 时代的多个泄漏进程。 修法:对 pgrep 命中的 PID 逐个 ps 核对命令行,crashpad 类不算存活信号; 应用真正运行时主进程 / ZCode Helper 必然在列,不受影响。 新增 6 个单测(fake pgrep/ps + 真实 ps 集成 + 纯函数注入): - pgrep 无命中 → False - 仅 crashpad 僵尸(跨版本堆积)→ False - crashpad + 真实 helper 并存 → True - pgrep 命中后进程消失(ps 查不到)→ 按已退出处理(此用例在实现阶段 抓到了 None 分支误判存活的真 bug,已修) - _pid_commandline 真实子进程集成 - _pgrep_hits_are_alive 注入 cmdline_of 的纯函数测试 另经本机真机模拟验证:python 伪 crashpad 进程(argv 含 ZCode+crashpad 标记)+ 真 pgrep/ps,仅僵尸判 False、混入真实 helper 判 True、端到端判 True。
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
apply_after_exit.py currently unconditionally prepends HERE to sys.path during polling/install discovery, which can grow sys.path without bound in the watchdog loop and should be guarded to prevent long-running degradation.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
This PR fixes macOS end-to-end usability of the “apply after exit” repack-level patches by making the watchdog and patcher cross-platform (POSIX) and by correcting process-detection so leaked chrome_crashpad_handler processes don’t block patch application.
Changes:
- Add POSIX-aware
zcode_running()logic that validatespgrephits via per-PIDpscommandline checks (filters crashpad leaks). - Make the asar write lock cross-platform (Windows
msvcrt.lockingvs POSIXfcntl.flock) and fix path handling in probes/tests. - Fix test flakiness around stale backup globbing and add POSIX-specific tests for
zcode_running()behavior.
| File | Description |
|---|---|
| tests/test_patcher.py | Fixes flaky stale-backup assertions and adds POSIX tests for crashpad-filtered running detection. |
| skills/zcode-tokenspeed/scripts/zcode_patcher.py | Adds PID commandline inspection for POSIX zcode_running, uses PureWindowsPath to avoid WindowsPath construction issues, and splits file locking by platform. |
| skills/zcode-tokenspeed/scripts/sync.py | Ensures the watchdog is spawned in a detached session on POSIX so it survives app exit/cleanup signals. |
| skills/zcode-tokenspeed/scripts/apply_after_exit.py | Reuses patcher’s cross-platform running detection, improves restart behavior per-platform, and logs fatal crashes for unattended watchdog runs. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| sys.path.insert(0, str(HERE)) | ||
| import zcode_patcher as zp | ||
| return zp.zcode_running() |
看护轮询每 3s 调一次 zcode_running(),原写法每次 sys.path.insert(0, HERE), 24h 上限约 2.9 万个重复项。改为只在缺失时插入,两处调用点(zcode_running / resolve_install)统一走 _import_patcher()。附回归测试:连续调用 200 次 sys.path 长度不变、模块对象复用缓存。 实际影响说明:import zcode_patcher 第二次起命中 sys.modules 缓存,不会逐项 扫描 sys.path,故原问题主要是内存/整洁性而非查找性能——但仍应修。
|
感谢指出,已在 b21dead 修复:两处调用点统一走 |
自查发现:上一提交为过 no-window 静态检查把它改成 **no_window_kwargs() (CREATE_NO_WINDOW),与上游的 DETACHED_PROCESS 不同——违背本 PR 「Windows 行为零变化」的承诺。静态检查本来就接受 creationflags 字面量, 无需改值。GUI 程序下两者效果几乎一致,但保持逐字节同语义更诚实。

现象
macOS 上 TPS 状态栏 / 思考滑条 / 模型拉取 / 增强提示词这 4 项重打包级补丁永远不生效:退出并重启 ZCode 后依然「未打」,
doctor.py一路全绿、不报任何错。根因(四层,逐层剥开)
1. 看护进程在 macOS 上启动即崩(无声)
apply_after_exit.zcode_running()直接跑["tasklist"]检索b"ZCode.exe"——Windows 专属。macOS/Linux 上FileNotFoundError未被捕获(except 只接 TimeoutExpired),看护第一次轮询就崩;启动方又把 stderr 定向 DEVNULL,崩溃完全无声。日志表现为「看护启动 N 次 / 等到退出 0 次 / 完成 0 次」。2. asar 跨进程写锁无条件
import msvcrt_AsarWriteLock的注释写着「本工具只在 Windows 注入客户端」,但插件明确支持 macOS(/Applications/ZCode.app)。于是 4 项重打包级补丁全部ModuleNotFoundError: No module named 'msvcrt'失败——第 1 层修好、看护真正开始执行时才暴露。3. crashpad 泄漏让 pgrep 永远非零
macOS 上 ZCode 每次退出都会泄漏一个
chrome_crashpad_handler(ppid=1,命令行仍含 ZCode.app 路径)。pgrep -f ZCode因此永远命中——用户明明已完全退出,看护仍判「在运行」,直到 24h 超时放弃。本机实测堆积过 3.9.1(8 月 24 日)/ 3.10.1(8 月 28 日)时代的泄漏进程,跨版本存活一个多月。这一层决定了「修好前两层之后补丁依然写不进去」。顺带:
pgrep -x ZCode在 macOS 上匹配不到主进程(主进程 argv 为裸ZCode,与 comm 不一致)。本 PR 不依赖-x,改为逐 PID 核对命令行。4. 探针
_from_running_processes在 POSIX 上跑单测必炸Path("C:\\...")在 macOS/Linux 抛NotImplementedError(单测把 os.name 伪造成 nt,而zp.os即全局 os 模块单例,Path()因此尝试构造 WindowsPath)。修复(6 个 commit)
a1c4102zcode_running复用跨平台检测、_find_exe(macOS .app 包 / Linux 小写命名)、重启命令按平台分流、start_new_session后台化(否则看护留在钩子进程组,应用退出时被整组信号连带杀掉)、崩溃写日志、Popen 补no_window_kwargs8173cbdmsvcrt.locking/ POSIXfcntl.flock分流;探针存PureWindowsPath,discover()里map(Path, roots)转回(Windows 真实运行路径行为不变)5492539.meta.json——修复一个上游 flaky:_archive_backup把 bak 与 bak.meta.json 一起改名成stale-*,两者都命中 glob 模式,APFS 目录枚举顺序随时间戳哈希变化,stale[0]取到谁是随机的(实测同代码 10 跑 4~6 挂);修复后 20 连跑全绿098f2ffzcode_running对 pgrep 命中的 PID 逐个ps核对命令行,排除 crashpad;应用真正运行时主进程 / ZCode Helper 必然在列,不受影响b21dead_import_patcher()守卫 sys.path 插入——看护轮询每 3s 调一次,原写法 24h 会堆积约 2.9 万个重复项;附回归测试(连续调用 200 次 sys.path 长度不变)3addc75creationflags=DETACHED_PROCESS(b21dead前为过静态检查误改为 CREATE_NO_WINDOW,与"Windows 行为零变化"承诺不符)测试
python -m unittest discover -s tests:275 个全绿(2 个 Windows-only skip),含新增 7 个(fake pgrep/ps 四种情形、真实 ps 集成、纯函数注入、sys.path 守卫回归;其中「pgrep 命中后进程消失按已退出处理」用例在实现阶段抓到了 None 分支误判存活的真 bug,已修)TimeoutError;释放后重新获取 ✓zcode_running()→ True ✓pkill清 crashpad 才能触发写入,修复后无需任何手动步骤验证环境:macOS arm64(darwin 25.6.0)、ZCode 3.14.3、Python 3.11.8。
备注
Windows 行为零变化:tasklist 分支、msvcrt.locking 路径、no_window_kwargs 语义均保持原样;新增的 ps 逐 PID 核对只在 POSIX 分支执行。