你让 Codex 列举 public/ 下所有资源,或者审计 data/ 下每个文件。Agent 回的列表里安静地漏掉了 PNG 雪碧图、sqlite seed DB、预编译的 .wasm、嵌入字体二进制。更糟的是它一句警告都没有,回复读起来好像那些文件根本不存在。等 reviewer 发现少了一项,你也说不清还有多少次审计静默丢了二进制。
最快的修法: 别再让 Codex 去”读”目录,让它去”探测”目录。先对每个条目跑 file、wc -c、sha256sum,只读 file 判定为文本的那些,再强制一个结构化输出——输入目录里每个文件都必须落进恰好一个桶(read_text、skipped_binary、skipped_too_large、skipped_ignored)。光这一步就让静默丢失变得不可能。本文剩下的部分解释丢失为什么会发生,以及怎么把每类二进制转成 agent 真能推理的东西。
有两件事在驱动这个行为,截至 2026 年 6 月,两件都是”按设计如此”:
- Codex 的
shell枚举走的是 ripgrep(CLI 里自带)。ripgrep 默认自动跳过二进制文件、自动遵守.gitignore/.ignore/.rgignore。所以rg --files、grep -r这类一行命令在模型看到之前就把二进制悄悄滤掉了。 - Codex 对命令输出有硬截断:大约 10 KiB 或 256 行,先到先截(保留头 128 行 + 尾 128 行,丢掉中间)。所以一个确实很大的”文本”文件会显得只读了一半或被跳过,很容易被误当成二进制那种情况。
修法不是强行读二进制(产出的是模型根本推理不了的垃圾),而是教 agent 用不同工具去承认二进制:hash、元数据、结构化探针。
常见原因
按命中率排序。
1. 枚举命令(rg / grep)自动跳过二进制
这是最常见、也最隐形的原因。Codex 用来跑 --files 和内容搜索的 ripgrep,会把任何含 NUL 字节的文件判定为二进制并默认跳过。如果你的 prompt 让 Codex”用 rg --files 列文件”或”grep 这个目录”,二进制在模型看到之前就被滤掉了,且没有任何提示。
如何判断:让 agent 改用 ls -la(或 find <dir> -type f)重新列,别用 rg。文件出现在 ls/find 里但没出现在 agent 之前的总结里,就是被搜索工具丢了。要坐实是 ripgrep 干的,跑 rg --files --binary <dir> 对一下数量——加了 --binary 二进制就回来了。
2. Read 工具读非 UTF-8 字节报错,agent 当”没这个文件”
当模型真去直接读一个二进制时,read 对非文本字节会返回 error 或空 body。Agent 不会换工具重试,直接跳过,文件就从它的认知里消失了。
如何判断:文件在 ls/find 里有,但 agent 在一次明确的读取尝试后说”读不了”或干脆再也不提它,就是这一类。跑 file <path> 确认它是非文本。
3. 扩展名不在 agent 的文本 heuristic 里
.parquet、.arrow、.msgpack、.protobuf、自定义 .bin——大多数 agent 的”文本”allowlist 没这些。甚至存成 .dat 的 JSON 也可能被当二进制跳过。
如何判断:拿 agent 真正读到的文本文件后缀对比一下。不在常见的 .md .txt .json .yaml .ts .js .py .go .rs .toml 之列的,就可能被静默跳过了。
4. 文件含 BOM 或非 UTF-8 编码
UTF-16 LE、Latin-1、UTF-8-with-BOM 这种可能让严格 UTF-8 reader 出错。Agent 当 encoding error 跳过、不把问题暴露出来。(注意:较新的 Codex 版本在加载 AGENTS.md 指令文件时,碰到非法 UTF-8 现在会告警而不是静默丢弃,但这个告警不延伸到你让它读的任意文件。)
如何判断:file path/to/file 显示 UTF-16 或 ISO-8859——严格 reader 大概率拒收了。
5. 文件是文本但超过输出截断上限
30 MB 的 CSV 是文本,但 Codex 把单条命令的输出截到约 10 KiB / 256 行(头 128 行 + 尾 128 行)。cat data.csv 返回头尾、中间被省略,于是 agent 只基于文件的一小部分推理,可能下结论说文件”基本是空的”或干脆跳过。
如何判断:wc -c file 返回一个很大的数,而 agent 的总结只覆盖文件的最上面和最下面,或者根本没提它。截至 2026 年 6 月,稳定版 CLI 里没有原生的 --max-file-size 旋钮;可配置的单条命令输出上限有人提过需求(issue #5913 和 #6426),但还没合入,所以这个得靠你自己切片(Step 5)来控制。
6. Symlink 指向二进制或 Git LFS 指针
Git LFS 在仓库里存一个小文本指针(约 130 字节),真实二进制留在 LFS 服务器。Codex 读到指针、看到一个小小的非内容 blob、当垃圾处理掉。
如何判断:cat file.psd 显示 version https://git-lfs.github.com/spec/v1 后跟一行 oid sha256:——真实二进制根本没拉下来。
7. .gitignore 把文件从 agent 视野里滤掉了
因为搜索路径走 ripgrep,凡是被 .gitignore、.ignore、.rgignore 命中的,对 rg 枚举都是不可见的。你一旦忘了某条 ignore 规则还在生效,就会以为 agent 看到了文件。
如何判断:git check-ignore -v path/to/file 返回匹配规则。注意:截至 2026 年 6 月,Codex 不遵守 .codexignore(open issue #6530、#2847,以及仍未关闭的需求 #24993)——只有 ripgrep 的 ignore 文件和 .gitignore 才真正藏住文件,所以别指望 .codexignore 来暴露或隐藏任何东西。
我属于哪一类?
| 症状 | 可能原因 | 先查什么 |
|---|---|---|
文件在 ls/find 里有,但 agent 输出里从没出现、也没尝试读 | 搜索工具自动跳过(#1)或 ignore 规则(#7) | rg --files <dir> vs find <dir> -type f;git check-ignore -v <path> |
| Agent 说”读不了”/read 返回空 | 非 UTF-8 读取报错(#2)或编码古怪(#4) | file <path> |
| 只有某些扩展名消失 | 扩展名不在文本 allowlist(#3) | diff 被跳过 vs 读到的扩展名 |
| 大文本文件只总结了一半或被丢 | 输出截断(#5) | wc -c <path> |
| 一个内部带 URL 的小”资源”文件 | LFS 指针(#6) | cat <path> 显示 git-lfs spec 行 |
动手前先确认
- 跑
find . -size +1M -type f | head看可能被跳过的有哪些。 - 跑
file path/to/each确认哪些真是二进制、哪些是被误判的文本。 - 跑
rg --files <dir> | wc -l和find <dir> -type f | wc -l对比——差值就是你的二进制/被 ignore 的数量。 - 决定 agent 到底需要每个二进制的什么:存在 + hash、尺寸、schema,还是完整字节读取(很少需要)。
需要收集的信息
ls -la <dir>(或find <dir> -type f)全输出 vs agent 认知的文件列表——diff 找出被丢掉的条目。- 每个被跳过文件的:扩展名、
file输出、字节大小、是否是 LFS 指针。 - Agent transcript 里那个文件本应出现但没出现的位置。
- 项目的
.gitignore、.ignore、.rgignore和.gitattributes(最后这个告诉你哪些扩展名被 LFS 跟踪)。
最短修复路径
按收益从高到低。
Step 1:先给 agent 一个二进制感知的探针工具
把”读这个文件”换成”探测这个文件”。放进 prompt 或 AGENTS.md:
对 data/ 下每个文件(用 find data -type f 枚举):
1. 跑:file <path> # MIME 类描述
2. 跑:wc -c <path> # 字节大小
3. 跑:sha256sum <path> # 内容 hash
4. 若 `file` 输出含 "ASCII text"、"UTF-8"、"JSON" 或 "Unicode text" → 读内容
5. 否则 → 只记 {path, type, size, sha256},不读字节
特意用 find(而不是 rg --files)枚举:find 不会自动跳过二进制、也不遵守 ignore 文件。Agent 现在知道二进制存在、是什么类型、指纹是什么——而不用碰字节。
Step 2:显式 allowlist 扩展名
在 task prompt 或 AGENTS.md 里:
可读文本扩展名:.md .txt .json .jsonl .yaml .yml .toml .ini
.ts .tsx .js .jsx .mjs .cjs .py .rb .go .rs .java .kt .swift
.html .css .scss .sql .sh .dockerfile .env.example
其他扩展名:只记 path + size + sha256。
不要试图读它们。
不要从输出里省略它们。每个都带 "binary" 标记列出来。
这条消除了静默丢失——每个文件都在输出里出现,要么有内容、要么有 binary 标记。
Step 3:把二进制转成 agent 能读的文本表示
如果内容确实重要,先转码成文本再喂进去:
| 二进制类型 | 转换命令 | 输出 |
|---|---|---|
| PNG / JPG | identify -verbose img.png | 尺寸、色彩空间、元数据 |
pdftotext file.pdf - | 提取的文本 | |
| sqlite | sqlite3 db .schema | schema DDL |
| parquet | python -c "import pyarrow.parquet as pq; print(pq.read_schema('file.parquet'))" | 列 schema |
| wasm | wasm-objdump -h file.wasm | section headers |
| zip / tar | unzip -l file.zip | 归档 manifest |
让每次转换都控制在约 10 KiB 的输出上限以内(比如 pdftotext file.pdf - | head -200),免得 Codex 把结果从中间截断。这样 agent 就基于内容推理,而不是基于字节。
Step 4:Agent 跑之前先解析 LFS 指针
如果 .gitattributes 列了 LFS 跟踪的扩展名:
git lfs install
git lfs pull
用 file my-asset.psd 确认——应该显示 Adobe Photoshop Image 而不是 ASCII text。不做这步 agent 永远只看到指针。在 CI 里预拉、而不是塞进 agent loop,免得一个几 GB 的拉取把会话预算炸掉。
Step 5:大文本文件读之前先切片
如果 wc -c data.csv 很大(哪怕几百 KB 都已经会撞上约 10 KiB / 256 行的上限):
head -200 data.csv > data.head.csv
tail -200 data.csv > data.tail.csv
shuf -n 200 data.csv > data.sample.csv
让 agent 读 head + tail + sample。30 MB 的 CSV 变成 600 行,大多数结构问题就够回答了,也不会触发截断。
Step 6:强制一个不可省略的输出 schema
让 Codex 产出一个物理上无法丢文件的结构:
输出 JSON:
{ "read_text": [...], "skipped_binary": [...], "skipped_too_large": [...], "skipped_ignored": [...] }
输入目录里每个文件必须出现在恰好一个数组里。
如果各数组总数 != `find <dir> -type f | wc -l` 的计数,就是错:重跑。
Schema 让静默丢失不可能发生,任何丢失都变成 agent 必须对账的、可见的数量不符。
怎么确认已经修好
- 把 agent 报告的文件列表和
find <dir> -type f做 diff——不应有任何缺失。 - 确认各数组总数和
find <dir> -type f | wc -l完全相等。 - 每个
skipped_binary条目记录的 sha256 和sha256sum <path>对得上。 - 新增一个二进制(比如
head -c 4096 /dev/urandom > <dir>/probe.bin)再跑一次——它必须出现在skipped_binary里,而不是消失。
长期预防
- 任何文件审计 prompt 默认用
read_text + skipped_binary + skipped_too_large + skipped_ignoredschema;绝不给隐式丢失留可能性。 AGENTS.md模板里 read 步之前永远先加一步 probe(file+wc -c+sha256sum)。- 需要二进制现身时用
find -type f枚举,别用rg --files;rg留给纯文本内容搜索。 - 维护一份项目级”二进制但有用”格式清单 + 对应转换命令(PDF →
pdftotext、sqlite → schema 等)。 - 任何要让 agent 看资源的 sandbox 先跑
git lfs pull。 - 每季度审一次
.gitignore/.ignore/.rgignore,搞清楚 ripgrep(也就是 agent)被禁止看到什么——并记住.codexignore目前不起任何作用。 - RAG 索引或微调 pipeline,在模型看到语料之前显式做一步”二进制 vs 文本”分拣。
常见坑
- 把 4 MB 二进制 base64 塞进 prompt”让它看见”——浪费上下文,反正模型也无法基于原始字节推理。
- 觉得
.json永远安全可读——50 MB 的 JSON dump 远超 10 KiB 输出上限,会被截成头 + 尾。 - 指望
.codexignore来对 agent 藏住密钥或噪音——Codex 目前不遵守它(issue #6530);规则放.gitignore或.ignore里。 - 忘了
.gitignore里的任何东西对rg枚举都不可见,所以.env、dist/、构建产物根本到不了 agent 手里。 - 把 UTF-16 文件永久当”二进制”——
iconv -f UTF-16 -t UTF-8 file就修好了。 - 把巨大的
git lfs pull塞进 agent loop 而不是在 CI 里预拉。
常见 FAQ
Q:Codex 说”file not found”,但 shell 里能 cat 出来,为什么?
通常它读了文件、编码把 read 工具搞挂了,表面成了”not found”或空 body。file <path> 跑一下,如果非 UTF-8 先用 iconv 转码再读。
Q:我的 agent 用 rg --files 枚举了目录,二进制都没了,这是 bug 吗?
不是,这是 ripgrep 的设计行为:它跳过二进制文件、遵守 .gitignore。需要列出二进制时改用 find <dir> -type f(或 rg --files --binary)。
Q:Agent 把 .png 读成一大段乱码输出,怎么阻止?
你有一条宽松的读取路径,把原始字节倒出来了。显式 allowlist 文本扩展名(Step 2),其余一律走 probe 工具(Step 1)。
Q:为什么不直接对每个文件 hash + describe,不管类型?
完全可以——又快又安全。大多数模板仍然读文本内容,是因为源码读出来比 hash 有用。但 public/、data/、assets/ 这种,默认 probe-only 才是对的。
Q:Codex 有没有 --max-file-size 旗标把上限调高?
截至 2026 年 6 月,稳定版 CLI 没有。输出按每条命令约 10 KiB / 256 行截断(头 128 行 + 尾 128 行),可配置的单条命令输出上限有人提过需求(issue #5913 和 #6426),但还没合入。现阶段只能自己切大文本文件(Step 5)。
Q:.codexignore 能不能让 agent 远离某个文件夹?
不能。截至 2026 年 6 月 Codex 不读 .codexignore(issue #6530 / #2847)。用 .gitignore 或 .ignore(ripgrep 遵守)来藏路径,或者把排除项作为软指令写进 AGENTS.md。
相关阅读
- Codex Agent 在大仓库里失去上下文
- Codex Agent 任务执行到一半就停
- Codex 不照你的项目结构来
- Codex 重复创建文件而不是改文件
- Codex 环境配置失败
- Codex 漏项目特定约定
- Codex Agent 输出与 Prettier 冲突:6 个原因 + format-in-loop 模板
外部参考: