Codex Agent 静默跳过含二进制数据的文件:7 个原因 + probe 替代方案

Codex 把 PNG / PDF / sqlite / 编译产物从审计里悄悄丢掉。修法:用 file + wc -c + sha256sum 探测二进制、文本扩展名 allowlist、强制不可省略的输出 schema。

你让 Codex 列举 public/ 下所有资源,或者审计 data/ 下每个文件。Agent 回的列表里安静地漏掉了 PNG 雪碧图、sqlite seed DB、预编译的 .wasm、嵌入字体二进制。更糟的是它一句警告都没有,回复读起来好像那些文件根本不存在。等 reviewer 发现少了一项,你也说不清还有多少次审计静默丢了二进制。

最快的修法: 别再让 Codex 去”读”目录,让它去”探测”目录。先对每个条目跑 filewc -csha256sum,只读 file 判定为文本的那些,再强制一个结构化输出——输入目录里每个文件都必须落进恰好一个桶(read_textskipped_binaryskipped_too_largeskipped_ignored)。光这一步就让静默丢失变得不可能。本文剩下的部分解释丢失为什么会发生,以及怎么把每类二进制转成 agent 真能推理的东西。

有两件事在驱动这个行为,截至 2026 年 6 月,两件都是”按设计如此”:

  • Codex 的 shell 枚举走的是 ripgrep(CLI 里自带)。ripgrep 默认自动跳过二进制文件、自动遵守 .gitignore / .ignore / .rgignore。所以 rg --filesgrep -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-16ISO-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)来控制。

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 fgit 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 -lfind <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 / JPGidentify -verbose img.png尺寸、色彩空间、元数据
PDFpdftotext file.pdf -提取的文本
sqlitesqlite3 db .schemaschema DDL
parquetpython -c "import pyarrow.parquet as pq; print(pq.read_schema('file.parquet'))"列 schema
wasmwasm-objdump -h file.wasmsection headers
zip / tarunzip -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_ignored schema;绝不给隐式丢失留可能性。
  • AGENTS.md 模板里 read 步之前永远先加一步 probe(file + wc -c + sha256sum)。
  • 需要二进制现身时用 find -type f 枚举,别用 rg --filesrg 留给纯文本内容搜索。
  • 维护一份项目级”二进制但有用”格式清单 + 对应转换命令(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 枚举都不可见,所以 .envdist/、构建产物根本到不了 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 #排查 #binary-files #encoding