【Bug已解决】Codex CLI 报错 EMFILE: too many open files 解决方案

1. 问题描述

让 Codex 处理一个规模较大的项目(比如文件数量众多的 monorepo)时,任务执行到某个阶段突然崩溃,报出文件描述符耗尽的错误:

Error: EMFILE: too many open files, open '/path/to/project/src/xxx.ts'

1.1 具体现象

  1. 小项目完全没问题,一旦项目规模变大(文件数达到几千甚至上万),就容易触发
  2. macOS 上遇到的概率明显高于 Linux 服务器(默认文件描述符限制不同)
  3. 让 Codex 执行涉及大范围文件扫描/监听的任务(比如"检查整个项目里所有未使用的导入")时更容易复现
  4. 重新运行同样的任务,有时候能成功,有时候还是报同样的错

这个问题的根因是操作系统对单个进程能同时打开的文件描述符数量有上限,当 Codex 在处理大型项目时短时间内并发打开了大量文件(读取内容、监听变化等),超过了这个上限就会触发 EMFILE 错误。

2. 原因分析

每个进程在类 Unix 系统上都有一个"最大可打开文件数"的资源限制(ulimit -n),这个限制默认值在不同系统上差异很大:

系统 默认单进程文件描述符限制(典型值)
macOS(默认) 256(部分版本更低)
大多数 Linux 服务器发行版 1024(部分现代发行版更高)
Docker 容器(继承主机默认值) 因基础镜像和主机配置而异

当 Codex 处理大型项目时,如果需要同时打开大量文件(比如递归读取整个目录树进行代码分析、或者启用了文件变化监听功能),一旦并发打开的文件数超过这个系统限制,操作系统会直接拒绝后续的文件打开请求,抛出 EMFILE

用一张流程图梳理触发过程:

Codex 执行大范围文件扫描/分析任务
        ↓
逐个/并发打开项目内的文件进行读取
        ↓
当前进程已打开的文件描述符数量是否超过系统限制?
   ├─ 未超过 → 正常继续处理
   └─ 已超过 → EMFILE: too many open files

3. 解决方案

方案一:提高当前 Shell 会话的文件描述符限制(最直接)

# 查看当前限制
ulimit -n

# 临时提高限制(仅对当前终端会话生效)
ulimit -n 10240

# 在该终端会话中重新运行 Codex
codex "帮我分析整个项目的代码结构"

方案二:macOS 上永久提高系统级文件描述符限制

macOS 默认的限制值相对偏低,如果经常处理大型项目,建议做永久性调整:

# 编辑 /etc/sysctl.conf(如果不存在则新建)
sudo tee -a /etc/sysctl.conf <<EOF
kern.maxfiles=65536
kern.maxfilesperproc=32768
EOF

# 同时在 shell 配置文件中设置默认的 ulimit
echo 'ulimit -n 10240' >> ~/.zshrc

重启电脑后配置生效。

方案三:缩小单次任务处理的文件范围

不要让 Codex 一次性扫描整个超大型项目,而是把任务范围限定在具体的子目录或模块内:

❌ "帮我检查整个项目里所有未使用的导入"
✅ "帮我检查 src/modules/user 目录下所有未使用的导入"

分模块处理不仅能规避文件描述符耗尽的问题,也能让每次任务的反馈更聚焦、更容易验证。

方案四:项目中排除不必要的目录,减少扫描范围

确认项目根目录下的 .gitignore(以及 Codex 是否支持类似的忽略规则配置)是否正确排除了 node_modules、构建产物目录等不需要被扫描的内容,这些目录往往包含海量文件,是文件描述符被大量占用的常见来源:

# .gitignore
node_modules/
dist/
build/
.next/

方案五:Docker 容器场景下调整容器的文件描述符限制

如果 Codex 是在容器内运行的,需要单独为容器配置更高的文件描述符限制,主机系统的调整不会自动应用到容器内:

docker run --ulimit nofile=10240:10240 your-image codex "..."

或在 docker-compose.yml 中配置:

services:
  codex-runner:
    image: your-image
    ulimits:
      nofile:
        soft: 10240
        hard: 10240

4. 各方案对比总结

方案 适用场景 推荐指数
临时提高 ulimit 快速解决当前会话的问题 ⭐⭐⭐⭐
macOS 永久调整系统限制 长期需要处理大型项目 ⭐⭐⭐⭐⭐
缩小任务处理范围 长期有效的使用习惯优化 ⭐⭐⭐⭐⭐
排除不必要的扫描目录 项目中包含大量无关文件 ⭐⭐⭐⭐
Docker 容器调整限制 容器化运行场景 ⭐⭐⭐⭐

5. 常见问题 FAQ

5.1 为什么 Linux 服务器上遇到这个问题的概率比 macOS 低?

主流 Linux 发行版的默认文件描述符限制通常比 macOS 默认值更高,且服务器场景下运维人员往往已经针对高并发场景做过系统调优,所以相同规模的项目在 Linux 上触发 EMFILE 的概率相对更低,但并不代表完全不会遇到。

5.2 提高了 ulimit 之后,是不是意味着可以无限制地处理任意大小的项目?

不是。提高限制只是扩大了"安全边界",如果项目规模继续增长到远超新设置的限制值,问题依然会复现。更根本的长期做法是结合方案三(缩小任务范围)和方案四(排除无关目录),从任务设计层面降低对文件描述符的峰值需求。

5.3 这个问题和前面提到的 ENOSPC(磁盘空间不足)是同一类问题吗?

不是。EMFILE 是"同时打开的文件数量"超限,ENOSPC 是"磁盘剩余空间"不足,两者是完全独立的系统资源维度,报错信息和排查方向都不同,不要混淆。

5.4 团队里所有开发机都需要处理同一个大型 monorepo,是否要统一调整系统限制?

建议在团队的开发环境初始化文档/脚本中,统一写明推荐的 ulimit 配置和相关系统调优步骤,作为处理该 monorepo 项目的标准环境要求,避免每个人各自单独踩坑摸索。

5.5 排查清单速查表

□ 1. 用 ulimit -n 查看当前系统的文件描述符限制
□ 2. 尝试临时提高限制后重新运行任务
□ 3. macOS 用户考虑永久调整系统级限制配置
□ 4. 检查项目中是否有可以排除的大型无关目录(node_modules 等)
□ 5. 评估是否可以把任务范围缩小到具体子目录/模块
□ 6. 容器化场景确认容器自身的文件描述符限制配置

6. 总结

EMFILE: too many open files 报错的本质是Codex 在处理大型项目时并发打开的文件数量超过了操作系统对单进程的文件描述符限制,是系统资源边界触发的正常保护机制,而非软件缺陷。核心处理思路:

  1. 短期应急可以直接提高 ulimit 限制,快速解决当前的任务需求;
  2. 长期来看,缩小单次任务处理的文件范围、排除无关的大型目录,是更根本的优化方式;
  3. 容器化运行场景需要单独为容器配置文件描述符限制,主机系统的调整不会自动生效在容器内。

最佳实践建议:处理超大型项目时,把"任务范围拆分到合理的模块粒度"作为使用 AI 编程工具的一项基本习惯,这不仅能规避这类系统资源限制问题,也能让每次任务的执行结果更容易验证和把控。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐