一次 Codex Desktop 把本机 CPU 点燃的排障:Crashpad、GPU 与 syspolicyd

一次 Codex Desktop 把本机 CPU 点燃的排障:Crashpad 孤儿、GPU 进程与 syspolicyd

2026-06-08 晚上,本机突然出现明显卡顿,活动监视器里最刺眼的几项是:

  • syspolicyd 超过 150%
  • 两个 browser_crashpad_handler 各自超过 100%
  • Codex (Service) 超过 100%

这类现场最容易误判成“系统自己抽风”或“某个前台应用太重”。这次比较有意思的地方在于,三个热源其实分属两层故障:一层是安装器卷里跑出来的 Codex Crashpad 孤儿进程,另一层是当前 Codex Desktop 的 GPU 辅助进程稳定 100% 复发,并把 macOS 安全策略服务一起拖进高负载。

第一层:从安装器卷跑出来的 Crashpad 孤儿

进程列表里最热的 browser_crashpad_handler 并不是当前 /Applications/Codex.app 的子进程,而是来自:

/Volumes/Codex Installer/Codex.app/.../browser_crashpad_handler

它们的父进程已经变成 launchd,而 hdiutil info 和挂载表里已经看不到对应的 Codex Installer 卷。也就是说:安装器卷里的 Codex 曾经启动过,卷后来消失了,但 Crashpad 自监控进程留在系统里继续空转。

普通 TERM 没有把它们带走,最后用强制结束才止住这一层。强杀后,来自 /Volumes/Codex Installer 的高 CPU Crashpad 没再回潮。

经验:看到 browser_crashpad_handler 高 CPU 时,不要只看进程名。路径比名字重要。尤其要区分:

  • /Applications/Codex.app/...:当前安装版
  • /Volumes/Codex Installer/Codex.app/...:安装器卷或临时卷里的副本

后者如果父进程丢失,就优先按孤儿进程处理。

第二层:Codex 的 GPU 辅助进程稳定复发

第一层处理后,当前 Codex 里又出现了新的热点:

Codex (Service) --type=gpu-process

它稳定跑到 90%-100% CPU。杀掉这个 GPU 进程后,Codex 主进程会马上重建一个新的 GPU 进程,新进程继续 100%。这说明它不是单个子进程僵死,而是当前运行态的 GPU 路径会稳定触发。

临时止血方法是暂停这个 GPU 进程,而不是继续杀:

kill -STOP <gpu-process-pid>

暂停后,该进程 CPU 立即降到 0,Codex 主进程和后台对话仍然存活。代价是窗口渲染可能变慢或局部冻结,但比直接退出整个 Codex 更适合现场抢救。

同时,本地 Chromium 状态里可以看到硬件加速曾经是开启路径:

hardware_acceleration_mode_previous: true

因此做了持久规避:把本地状态改为下次启动禁用硬件加速,并把 GPU / Shader / Dawn 缓存目录移到带时间戳的备份目录。这个改动不修改应用包,不破坏签名,下一次重启 Codex 后生效。

第三层:syspolicyd 为什么也被拖高

排查中还有一个非常有辨识度的信号:spctl/Applications/Codex.app 做执行评估时报:

/Applications/Codex.app: Too many open files

系统日志里同时出现:

UNIX error exception: 24
Failed to generate SecStaticCode ... error: 100024
Security policy would not allow process: ..., /Applications/Codex.app/Contents/MacOS/Codex

这里的关键是 error 24,也就是文件描述符相关错误。它让 syspolicyd 进入高负载的安全评估状态。由于这台机器开启 SIP,尝试重启 com.apple.security.syspolicy 会被系统拒绝:

Operation not permitted while System Integrity Protection is engaged

所以最终策略不是硬重启系统服务,而是停止继续喂它更多高频评估请求,先把 Codex GPU 热点压住,再等待它自然回落。必要时,完整的收尾动作应该是退出并重新打开 Codex,让“禁用硬件加速 + 清空 GPU 缓存”的改动生效。

最小处置顺序

这次比较可靠的处置顺序是:

  1. 先按 CPU 排序确认真实热点,不凭活动监视器截图猜。
  2. /Volumes/Codex Installer 里的 Crashpad 孤儿进程强制结束。
  3. 对当前 Codex 的 --type=gpu-process 先验证:杀掉是否会马上重生并继续 100%。
  4. 若稳定复发,暂停 GPU 进程临时降温。
  5. 写入禁用硬件加速的本地状态,并备份移走 GPU / Shader / Dawn 缓存。
  6. 下次重启 Codex 后验证是否还会创建高 CPU GPU 进程。

有趣的地方

这次现场像三层套娃:

  • 最开始看起来是 syspolicyd 爆了。
  • 往下看,是 Crashpad 爆了。
  • 再往下看,Crashpad 里一部分来自已经消失的安装器卷,另一部分只是正常挂着。
  • 真正会复发的核心,则是当前 Codex Desktop 的 GPU 子进程。

最容易踩坑的是把所有 browser_crashpad_handler 都当成一类,或者把 syspolicyd 当根因。实际上 syspolicyd 更像被拖下水的裁判:它在不断判定一堆失败的执行/签名/安全评估请求,最后自己也开始发热。

后续建议

短期建议:

  • 避免从 .dmg 或安装器卷里直接长期运行 Codex。
  • 如果 Codex 窗口再次卡顿,优先看 Codex (Service) --type=gpu-process
  • 如果 GPU 进程再次 100%,先暂停或退出重开 Codex,不要反复杀,因为它会自动重生。

产品侧建议:

  • Codex Desktop 可以提供一个用户可见的“禁用硬件加速 / 安全渲染模式”开关。
  • GPU 进程连续重启或持续 100% 时,主进程应该自动退到软件渲染。
  • Crashpad 自监控进程应更积极地处理父进程丢失和应用卷消失的场景。

这次的教训很朴素:本机 CPU 爆炸时,不要只问“哪个名字最高”,要问“这个进程从哪里来、父进程是谁、杀掉后会不会重生、路径是不是当前安装版”。路径、父子关系、复发性,比单点 CPU 数字更接近真相。