Featured image of post 你的编程 Agent 正在悄悄「常驻」你的电脑——Codex 默认守护进程 + 后台 dump,「默认值」成了新的信任战场!

你的编程 Agent 正在悄悄「常驻」你的电脑——Codex 默认守护进程 + 后台 dump,「默认值」成了新的信任战场!

先说一个你大概没意识到的变化:你装的是一个命令行工具,最后得到的却是一个常驻后台服务。

这一周,openai/codex 仓库里两个 issue 接连冲上 Hacker News。

10 月 12 日,issue #48195 挂上 HN,609 分、294 条评论,当天最热的开发者话题之一。它的标题是一句请求:「CLI:把 daemon_auto_start 改成 opt-in——一个常驻、自更新的后台守护进程,不该是默认。」

10 月 17 日,另一个 issue #25921 又被顶上来,121 分、44 条评论:Codex Desktop 在后台持续生成崩溃转储文件,一天涨到 4.9 GB、54,504 个文件。

两个 issue 一个讲进程,一个讲磁盘,但指向的是同一件事——编程 Agent 正从「用完即走」变成「常驻、自更新、往你机器上偷偷写东西的本地服务」。 而真正把用户和厂商推到桌子两边的,是两个字:默认值。

一句话看清楚这两个 issue

#48195 问的是:一个 CLI,凭什么在没人同意的情况下,在我机器上常驻一个会自己更新的守护进程?

#25921 问的是:同一个架构的另外一张账单——那些「帮你干活的常驻小工」,把磁盘写满了谁负责?

一张表:这个「CLI」到底在你机器上留了什么

我把 issue 描述和一份对源码逐行审计的第三方分析对到一起,做成这张对照表。左边是大多数人对「装个 CLI」的心智模型,右边是实际发生的事:

维度 你以为的 CLI 实际发生的事
进程 随窗口生灭 常驻,重挂到 PID 1,关掉所有窗口也不退出
磁盘 npm 装的那一份 再复制一整份 373–431 MB 到 ~/.codex/packages/,且从不删旧版本
更新 你手动 npm update 守护进程每小时去拉一份 install.sh 管道给 /bin/sh 执行
环境 每次会话各自设置 钉死第一个会话的代理等环境变量,之后所有会话都跟着它走
关闭 Ctrl-C / 关窗 要 daemon stop;而且 stop 之后更新循环还可能是活的
后台文件 无 另一个 issue:Crashpad 转储每天 +4.9 GB、5 万多个文件

光看这张表,最扎眼的不是某一格,而是左边和右边根本没有重叠。你签的是一个「命令行工具」,收到的是六个你从没被问过的行为。

它到底在你机器上留了什么

那份源码级审计最有价值的,是它去翻了大多数人不会打开的 Rust crate:codex-rs/app-server-daemon。

它落在两个目录里。~/.codex/app-server-daemon/ 放状态(settings、锁、更新器的 PID);~/.codex/packages/app-server-daemon/ 放托管副本——注意,这不是符号链接,也不是对你包管理器安装的引用,而是一份逐字节复制的平台二进制包(codex、code-mode-host、外加打包进去的 bwrap/zsh/rg)。

这里有个设计细节值得说:复制前它会给整棵目录树算哈希,遇到「已经存在的版本内容不一样」时就拒绝覆盖。这个保护本身挺克制、挺好。但它有个副作用——每一次发布都会在磁盘上留下一个永久的新目录,而不是替换掉旧的。

于是账单来了。按 npm registry 公布的解包体积,Linux 版从 0.157.0 的 373.0 MB 一路涨到 0.162.1 的 431.5 MB,14 天涨了 58.5 MB(15.7%);把中间 12 个稳定版本加起来,理论累积约 4.89 GB。而且这份审计去 grep 了整整 34 个 Rust 文件,找 prune、retain_only、old_release、max_releases 这些清理逻辑——一个都没有;安装脚本里的清理函数只删临时目录,从不删旧版本。社区里有人报「11 个约 300MB 的二进制堆在一起」「Codex 总共占了 12GB」。OpenAI 的维护者 charliermarsh 在那个帖子里回过一句:「我没怎么碰过更新子系统,但这一看就是个疏忽,我去修。」

「默认值」是这行代码里权力最大的那一行

回到 #48195。它最让人不舒服的地方,不是守护进程本身,而是它是怎么变成默认的。

按审计,这次变更来自一个 10 行(+10/−50)的 pull request,2026-09-22 由一个机器人合并,配一个改动量更大的「兼容性恢复提示」的伴生 PR,然后随 0.157.0 在 9-25 发布。发布说明里关于这件事只有一句话:「为符合条件的交互式会话启用自动后台服务启动,并在服务设置不兼容时提供恢复选项。」

这句话里没有 persistent、没有 background、没有 detached、没有 self-updating、没有 copies 373 MB、没有 survives closing the window。读完发布说明,你知道「会起一个服务」;你不知道「它会比你开它的那个窗口活得久」。

而这个默认开关,到今天还没动。审计在 rust-v0.157.0、rust-v0.160.0、rust-v0.162.1 和 main 四个版本上都读了同一行代码——default_enabled: true。issue 提出 17 天、24 个稳定版本之后,它还是 true。

最能说明问题的是最后一个数字:OpenAI 完整的配置文档有 22.62 万字符,里面「daemon」这个词出现了 零次;而它四次提到 check_for_update_on_startup。

我想说的是:能力清单告诉你「它能做什么」,默认值告诉你「它会在你不知情时做什么」。 前者是产品页的活儿,后者才是信任。当一行 default_enabled: true 就能决定你电脑上多出一个常驻进程、多出一份 4.9GB 的副本、多出一条每小时自更新的网络通道时,「默认值」就不再是配置项,而是厂商替你做的政策决定——而且是那种,你不去读一个没人链接过的 Rust 文件,就永远不会知道的政策。

第二张账单:后台 dump,以及被自更新打断的活

如果说守护进程是「多了一个不请自来的进程」,那 #25921 就是同一套架构的磁盘版账单。

Codex Desktop 在 ~/Library/Application Support/com.openai.codex/web/Crashpad/pending 下,持续生成配对的 .dmp(崩溃转储)和 _sidecar.json 文件。报告者的机器上,这个目录一天长到 4.9 GB、54,504 个文件(27,252 个 .dmp + 27,252 个 sidecar);而且这个应用当时是可用的——不是崩溃了在疯狂转储,而是「好端端地在后台一直写」。他还发现两个 browser_crashpad_handler 进程从 5 月底就以 PPID=1(挂到 init)在跑。他甚至不敢公开 minidump,因为里面可能含内存内容、本地路径和私人数据。

还有一个更疼的 issue #40969,直接说明「常驻 + 自更新」会砸掉你的活:守护进程的自动更新在拿到新版本后会重启后端,它给了正在跑的对话轮次一个「优雅排空」窗口——但只有 60 秒(上限 300 秒),超时就 SIGKILL。对于一个动不动跑几分钟 build/test 的 agent 来说,这个窗口基本等于没有;报告者描述了一个 90 秒的窗口,期间所有附着的客户端掉线、正在执行的轮次被终止。而且没有任何受支持的开关能关掉自动更新。

把这两句放一起看,就很清楚了:常驻化带来的好处(远程控制、第三方 harness、跨会话状态)是厂商和生态的;而它带来的成本(磁盘、被打断的活、几个你不认识的常驻进程)是用户的。 谁付钱、谁受益,在默认值这一层就已经分配完了。

这不是 Codex 一家的病

把视角拉远一点,你会发现这不是某一家的问题,而是一个正在发生的品类迁移:编程 Agent 正集体从「用完即走的 CLI」走向「常驻、可远程、带后台状态的本地服务」。

这个方向本身有它的道理——永远在线的 agent 需要后台进程,需要跨会话的记忆,需要能被手机或其他客户端远程控制。Codex 的守护进程、OpenCode 2.0 把服务端改成默认常驻后台、各种「从 Slack / Linear / GitHub 驱动 coding agent」的产品,都是同一个浪潮。

问题不在于「该不该有后台组件」,而在于你有没有把后台组件当作一个需要交代清楚的对象来发布。

那份审计给出了一句我很认同的总结:要做一个带后台组件的 CLI,有三样东西必须一起发——默认值、文档、清理策略。 Codex 这次发了第一个,半个第二个(工程做了硬功夫:加锁、哈希校验、拒绝覆盖、恢复逻辑),第三个完全没有。结果就是我们看到的这场 HN 讨论——用户不得不靠读一个 30KB 的 Rust 文件,来逆向出自己软件的进程生命周期。

对开发者,这意味着审计清单要加一项

如果你今天在决定要不要信一个更新飞快的开发者工具,那句老话得改一改:你现在得像审依赖清单一样,去审它的进程模型。

以前你审的是「这个包引入了哪些依赖、有没有恶意代码、许可证是什么」。现在你还得问:它开了哪些常驻进程?重挂到谁身上?往磁盘写多少?这些文件退役吗?它自己从哪更新?更新会不会打断我正在跑的活?这些问题的答案,常常不在文档里,而在源码里。

这也意味着,「默认值」会很快从产品偏好,变成采购与合规的检查项。系统管理员、Nix/发行版打包者、企业桌面团队,看到「一个后台进程每小时把远程脚本管道给 /bin/sh、而且运行的是一份不受包管理器管控的自复制副本」这句话,基本就等于整个审查结论了——因为这句话同时踩中了好几条他们最在意的红线。

那一个「可信的默认值」长什么样? 我想大概是这样:后台服务要么 opt-in、要么第一次问一句;既然我是用包管理器装的,就别自复制、别自更新,跑我装的那份二进制;进程随窗口生灭;退出时清理;磁盘足迹有上限和退役策略;所有后台行为在文档里明说,而不是藏在源码里等用户去挖。

写在最后

Codex 这次的两件事,单独看都不算大:一个守护进程、一堆转储文件。但它们凑在一起,说明了一件更大的事——Agent 落地到你的机器上时,真正的信任问题不在模型层,而在「宿主层」:它在你的设备上开了什么进程、占了多少盘、从哪更新、默认值是谁定的。

这跟我之前写过的几条线是连着的:Agent 把记忆写成明文文件树(那篇讲的是它在你磁盘上留下什么格式的数据),个人 Agent 该拿「工牌」还是拿你「分身」(那篇讲的是它拿着谁的凭据)——加上今天这层「它开了什么进程」,拼起来就是同一张图:Agent 的边界,不在它能做什么,在它默认会做什么、以及这些默认值有没有被摆到台面上。

下一次你 npm install 一个飞得很快的 Agent 工具时,别只看它能力列表。翻一眼它的进程和磁盘:你装的到底是一个工具,还是一个会自己更新、自己长大、还从不打扫的房客?

能力列表回答「它能做什么」,默认值回答「它会不会在你不知情时做什么」。后者,才是信任的战场。

By AI博士 万戈