Bun 运行原生 CLI:管道输出截断并报 EAGAIN
Bun 1.4.4 在 Linux 上运行含同步原生写出的 CLI 时,stdout 管道可能变成非阻塞并触发 EAGAIN。检查原始输出完整性,按工具支持的 Node 入口运行。
格式化工具退出码是零,重定向文件却只剩开头一小段;终端或日志里还出现 EAGAIN。如果命令通过 bun --bun 运行,工具又包含同步写 stdout 的原生模块,应检查 Bun 下的标准输出兼容性。
在 Bun 1.4.4 的这条 Linux 原生同步写出路径中,原本超过一兆字节的完整输出进入管道后只留下 8192 字节。8192 是该环境下看到的结果,不能当作 Bun 对所有输出设置的固定上限。
看是谁向管道写数据
这条路径中,CLI 接触 process.stdout 后,文件描述符 1 处于非阻塞模式。同步原生写出遇到暂时写不进去的管道,会返回 EAGAIN。如果原生绑定没有等待可写并继续发送剩余数据,最终文件就会缺内容。
有的兼容代码会调用:
process.stdout._handle?.setBlocking(true);
受影响环境里 _handle 不存在,可选链让这句静默跳过。没有抛异常,不代表输出已经切回阻塞模式;_handle 本身也不是跨运行时可靠的公共接口。
先记录实际执行的二进制、CLI 版本、stdout 是终端还是管道,以及发生截断时的 stderr。对生成 JSON、代码或补丁的命令,保留未覆盖业务文件的原始输出,检查字节数和格式完整性。不要把截断结果继续当作有效产物提交或部署。
按工具支持的运行时入口调用
若这个 CLI 的正常入口和原生模块支持的是 Node,先撤掉强制使用 Bun 的 --bun,沿包原有的可执行入口运行,并确认它实际采用了预期 Node 版本。也可以按工具文档,用 Node 执行其真实入口文件;不要猜测安装包内部路径,也不要只把日志里的命令名称改成 node。
这是一项针对运行时兼容路径的临时处理,业务参数、格式化选项和输入文件应保持一致。切换前保留原命令和失败产物,以便比较输出,不能顺手更新一整套工具链后把差异都归为这次处理。
如果写出代码由项目自己维护,应在流接口上处理背压,或在原生写出层正确处理暂时不可写与部分写入。第三方原生绑定没有这样做时,外围加一个 await 或延迟退出,无法自动补发它已经丢弃的剩余字节。
别把另外两类截断办法搬过来
exec() 的 maxBuffer 限制发生在父进程收集输出时,扩大它不会改变子进程描述符的非阻塞状态。把 process.exit() 改成 process.exitCode 适用于提前结束尚未完成的异步写出,也不能修复原生同步写出对 EAGAIN 的遗漏。
不要通过吞掉 EAGAIN、忽略 stderr 或不断忙重试来让任务变绿。尤其不能以退出码零作为生成文件可用的唯一条件。
调整后,在独立输出文件上核对完整字节数、结尾和语法,检查下游是否能消费全部内容;有条件时还应覆盖接收方较慢的管道。终端上短输出正常,仅说明那次输出没有触发相同压力,不能代替原故障场景的验收。若改回 Bun 运行,应在拟采用的正式版本重新覆盖相同原生模块与管道条件,确认完整输出后再切换。


