shell command 响应截断:把命令执行变成可控的信息管道
TL;DR
Codex 能跑 shell,不等于模型真的“看见了终端”。一次工具调用会经历命令构造、沙箱和审批、stdout/stderr 收集、响应预算压缩、上下文注入几个环节。输出太长时,模型拿到的往往只是被截断后的片段;错误堆栈、测试摘要、真实失败原因可能落在没有返回的中间部分。中高级团队使用 Codex 时,应该把 shell 当成受限信息管道:先定义问题,再让命令只吐出能决策的证据;完整日志留在工作区,进入对话的只应是摘要、定位线索和下一步命令。
读者定位
本文面向已经让 Codex 参与本地开发、排障、测试和代码审查的开发者,也面向要给团队制定 agent 工作规范的技术负责人。你需要理解基本 shell、CI 日志、测试框架和权限边界,不需要了解 Codex 内部实现。读完以后,你应该能判断什么时候让 Codex 直接执行命令,什么时候要求它先缩小范围,什么时候把大输出落盘,再用二次检索读取关键片段。
问题:长输出会制造“假上下文”
很多 Codex 任务失败,不是因为模型不会修代码,而是因为命令返回的信息不可读。比如一个 monorepo 的 npm test 可能吐出几千行;一个 pytest -vv 会同时包含 fixture 日志、warning、失败堆栈和 coverage;一个 docker compose logs 会把多个服务的历史输出混在一起。模型看到一大段文本后,会自然地从可见片段里推断原因,但真正的失败点可能被截断、折叠或淹没。
这种失败有三个典型表现。第一,模型抓住无关 warning 改代码,真正的 assertion 失败还在日志后半段。第二,模型看到最后的 “exit code 1”,却没看到前面哪个命令失败,只能猜。第三,模型为了找线索继续跑更大的命令,输出越来越长,上下文越来越脏,最后开始重复无效尝试。人类排障时会先过滤,agent 也需要同样的约束。
官方 CLI 文档把 --sandbox、--ask-for-approval、--dangerously-bypass-approvals-and-sandbox 放在核心选项里,是因为 shell 不是普通文本工具。它既能读取和修改文件,也能联网、安装依赖、推送代码、删除目录。官方 sandbox 文档也说明,沙箱约束的是 Codex 生成并派生出来的命令,审批策略决定何时暂停让人确认。输出截断看似是可读性问题,放在工程现场却会和安全、权限、审计一起出现。
心智模型:四段管道,而不是终端转发
可以把一次 shell 调用理解成下面的管道:
任务问题 -> 命令计划 -> 执行边界 -> 原始输出 -> 可读证据
任务问题决定命令应该回答什么。命令计划决定使用 rg、git diff、测试框架还是日志工具。执行边界由沙箱、permission profile、审批规则和当前工作目录决定。原始输出可能很大、很脏、包含敏感内容。可读证据才是应该进入模型上下文的内容。
这个模型会改变命令写法。不要问“能不能跑 npm test”,要问“我需要证明哪个改动有没有破坏哪个测试”。不要问“能不能看日志”,要问“要从哪一个服务、哪一个时间窗口、哪一个错误码开始过滤”。不要把 stdout 当作永久记录,完整记录应该写到可控文件,模型只读取和解释必要片段。
对团队来说,这个心智模型还避免了另一个误区:把截断当成模型能力问题。模型不是日志数据库。它只能基于当前对话里的证据推理。把三万行日志塞进工具响应,只是把检索工作交给一个会被上下文限制影响的推理模型。更稳的做法是让 shell、测试框架和搜索工具先完成粗筛,模型负责解释筛选后的证据。
详细机制:命令、审批、输出和上下文怎样互相影响
命令不是孤立文本
Codex CLI 的命令选项允许设置工作目录、模型、审批策略和沙箱模式。--cd 改变命令起点,--sandbox 选择 read-only、workspace-write 或 danger-full-access,--ask-for-approval 选择 untrusted、on-request 或 never。这些配置影响命令是否能执行,也影响模型接下来应该怎样解释失败。
例如在 read-only 下,写文件、跑会产生缓存的测试、安装依赖都可能被拦下。看到失败时,不能直接推断代码坏了,要先判断是不是权限导致。workspace-write 允许在工作区内编辑和运行常规命令,但越界访问、联网或更高风险动作可能需要审批。danger-full-access 移除沙箱边界,输出虽然更自由,风险也更大。
stdout/stderr 不是事实全量
工具响应通常会带退出码、耗时和一段输出。这里的输出是对话可承载的片段,不是可靠的全量日志仓库。长日志被截断后,开头、结尾或中间哪部分可见取决于具体工具实现和输出预算。工程上不能把“没看到错误”理解成“没有错误”,只能理解成“当前返回片段里没有错误”。
正确做法是让命令自己生成结构化或短输出。例如测试框架支持只输出失败摘要时,优先使用失败摘要;搜索时用 rg -n 定位行号,而不是整文件打印;日志查询用时间窗口、服务名、错误码过滤;需要完整日志时,写入 .codex-tmp/ 或仓库约定的临时目录,再按关键词、行号和尾部窗口读取。
管道会影响审批判断
官方 rules 文档说明,Codex 对 shell wrapper 和复合命令会做保守处理:简单线性脚本可以拆成单个命令应用规则,包含重定向、变量、通配符、替换和控制流等高级特性时,会把整个 wrapper 当成一个调用。也就是说,一条看似方便的 bash -lc "cmd1 && cmd2 > out" 可能让审批和规则判断变粗。
这对截断也有影响。复杂一行命令通常把执行、过滤、写文件、清理混在一起;失败时你不知道是哪个阶段坏了。给 Codex 的命令应该像排障步骤,而不是竞赛式 shell。尤其涉及删除、移动、批量改名、网络请求、凭据读取时,先列出目标,再确认路径,再执行动作,输出中保留足够证据。
环境变量和敏感输出需要前置控制
配置里的 shell_environment_policy 可以控制转发给派生命令的环境变量。团队不应该依赖模型“自觉不打印密钥”。如果某些命令可能输出 .env、token、私有日志或客户数据,应该从权限、命令和日志过滤三层控制:不读敏感文件,不运行会打印敏感内容的命令,必要时对输出做本地脱敏后再读。
这点在“让 Codex 查失败日志”时常被忽略。生产日志、CI secret masking 后的片段、云服务错误详情都可能含有账户名、内部域名或请求参数。截断不会保护秘密,反而可能留下无法解释的残片。把完整日志留在本地文件中,并只提取错误类型、堆栈顶部、相关测试名和时间戳,才是更可审计的方式。
真实工作流案例:修一个间歇性测试失败
假设团队的 React monorepo 有一个间歇性失败:CI 报 CheckoutSummary.test.tsx 失败,但本地不知道怎么复现。低质量用法是让 Codex 直接跑:
npm test
这个命令会输出所有包的日志。模型拿到的片段可能只有最后的覆盖率表,无法定位失败。更好的任务描述应该是:
先定位 CheckoutSummary.test.tsx 的失败摘要。不要直接打印完整测试日志。
如果需要完整输出,把日志写入 .codex-tmp/test.log,再用 rg 读取失败测试、assertion 和相关堆栈。
Codex 应该先执行短命令:
rg -n "CheckoutSummary" .
找到测试文件后,再跑聚焦测试:
npm test -- CheckoutSummary.test.tsx --runInBand 2>&1 | tee .codex-tmp/checkout-summary.log
这里仍要控制进入对话的内容。可以接着读取:
rg -n "FAIL|Expected|Received|CheckoutSummary|Error:" .codex-tmp/checkout-summary.log
如果失败堆栈被截断,再按行号取窗口:
sed -n '120,190p' .codex-tmp/checkout-summary.log
在 Windows PowerShell 中,对应写法可以是:
npm test -- CheckoutSummary.test.tsx --runInBand 2>&1 | Tee-Object .codex-tmp\checkout-summary.log
Select-String -Path .codex-tmp\checkout-summary.log -Pattern "FAIL|Expected|Received|CheckoutSummary|Error:" | Select-Object -First 80
这个流程的关键不是某个命令,而是证据链:先确定文件,再缩小测试,再保留全量日志,最后只把失败摘要喂给模型。模型做出的修改也更容易审查,因为每一步都有可复查的文件和命令。
操作清单:给团队落地的 shell 输出规范
- 搜索代码优先使用
rg或平台等价工具,并带上-n、目录范围和关键词,不让命令打印整个仓库。 - 运行测试时优先选择单包、单文件、单测试名;全量测试只作为最后验证,不作为第一轮信息收集。
- 大日志先写入工作区临时目录,例如
.codex-tmp/;进入对话的内容通过rg、Select-String、head、tail、sed -n等命令截取。 - 每条命令都应该回答一个具体问题:文件在哪里、失败是什么、差异有哪些、哪个配置生效。不能回答具体问题的命令先不要跑。
- 输出中出现密钥、cookie、私有 URL、客户数据风险时,停止扩大输出,改用本地脱敏或人工审查。
- 复合命令保持可读。涉及高风险动作时拆成“列出目标、确认路径、执行、验证”四步。
- 对失败命令记录退出码、失败摘要、相关文件和下一步假设,避免只记录“命令失败”。
- 在
AGENTS.md里写明本仓库的测试命令、日志目录、敏感文件规则和输出行数上限。
可以把下面这段写入团队级 AGENTS.md:
## Shell Output Policy
- Search with `rg` first and keep output scoped by path and pattern.
- Do not paste full test logs into the conversation. Save full logs under `.codex-tmp/` and read filtered snippets.
- Never print `.env`, credentials, production logs, customer data, or private API responses.
- For destructive or networked commands, list intent and target paths before execution.
- When a command fails, report the shortest useful failure summary and the command used to reproduce it.
权衡与风险
过滤输出会丢信息。tail -80 可能错过最早的错误,rg Error 可能漏掉非英文异常,失败摘要可能隐藏 setup 阶段的问题。所以策略不是“只看短输出”,而是“完整输出留存,短输出进入推理”。当短输出无法解释问题时,模型应回到日志文件按行号、关键词和时间窗口继续读取。
落盘日志也有风险。临时文件可能被提交,可能包含敏感内容,可能污染工作区。团队应该把 .codex-tmp/、tmp/agent-logs/ 等目录加入 .gitignore,并规定任务结束后清理。对受监管项目,日志留存还要符合数据保留政策,不应把生产数据复制到开发仓库。
审批也不是万能护栏。on-request 能在越界时暂停,但它无法判断一段业务 SQL 是否语义危险,不能理解某个内部脚本是否会触发外部通知,也不能保证命令输出不会泄露业务信息。高风险命令仍需要人工明确授权,最好由本地脚本把参数和目标限定住。
常见误区
误区一:把“命令跑完了”当成“模型理解了”。命令成功只说明进程返回了退出码,模型能理解多少取决于返回片段。长输出越多,越需要过滤。
误区二:让 Codex 先跑全量测试。全量测试适合最后确认,不适合作为初始诊断。初始诊断应该聚焦最近改动、相关测试和失败摘要。
误区三:把 tee 当成安全方案。tee 会同时把输出写文件并打印到对话。对超长或敏感日志,应先重定向到文件,再用筛选命令读取片段。
误区四:所有命令都写成一行。短期看节省交互,长期看降低审计性。复杂命令失败后,模型很难知道哪一步应该改。
误区五:相信截断会自动隐藏敏感信息。截断是容量控制,不是脱敏。敏感信息可能刚好出现在可见片段里。
团队落地:把输出治理写进工作流
真正难的不是知道 head、tail、rg 这些命令,而是让团队在压力下仍然使用它们。线上缺陷、CI 红灯、发布前临时失败,都会诱使开发者把完整日志直接丢给 Codex。因为这样看起来最快:先让模型看一切,再让它判断。但在大型仓库里,“看一切”往往等于什么都没看清。团队需要把输出治理做成默认工作流,而不是靠每个人临时自律。
第一步是为不同命令定义默认输出等级。搜索命令只返回路径、行号和少量上下文;测试命令先返回失败测试名、断言差异、堆栈顶部和复现命令;构建命令先返回失败包、失败阶段和错误码;日志命令必须带服务名、时间窗口和关键词。这样 Codex 在不同任务里拿到的是同一类证据,团队 review 时也能快速判断证据是否足够。
第二步是规定完整日志的存放位置。很多团队让 Codex 把日志写到任意临时文件,过几天仓库里出现一堆 output.txt、log.txt、tmp.log。这会污染工作区,也可能被误提交。建议统一使用一个被忽略的目录,例如 .codex-tmp/,并约定文件名包含日期、命令类别和任务关键词。任务结束时,Codex 应说明这些日志是否需要保留。对涉及客户数据或生产日志的仓库,默认不保留,除非人工要求。
第三步是给“继续读取日志”设计协议。模型第一次拿到失败摘要后,常常需要更多上下文。这时不应再运行全量命令,而应围绕已知行号和关键词补读。比如先看到 TypeError,再读取堆栈前后三十行;先看到失败测试名,再读取该测试文件;先看到服务启动失败,再读取配置加载位置。每次补读都应该回答一个具体假设。这样日志阅读会形成收敛路径,而不是重新泛滥。
第四步是把输出策略和安全策略合并。敏感内容不只来自 .env,还来自测试 fixture、错误对象、HTTP 请求头、数据库连接串、CI masking 前的原始日志。团队可以要求 Codex 在打印日志前先判断来源:本地单元测试输出风险较低,集成测试和生产日志风险较高,云服务诊断输出风险最高。风险较高时,先让人确认是否允许读取,或者只让 Codex读取本地脱敏后的字段。
第五步是训练 Codex 给出“证据摘要”,而不是只给结论。一个好的回复应该说:运行了什么命令,完整日志放在哪里,返回片段显示了什么,哪些关键内容没有被读取,下一步需要哪个文件或哪段日志。这样人类审查者能看到推理链。如果 Codex 只说“测试失败是因为类型不匹配”,却没有列出测试名、断言差异和相关文件,这个判断不应直接接受。
进阶场景:长任务中的输出预算管理
长任务里,输出截断的损害会叠加。第一轮看仓库结构,第二轮看配置,第三轮跑测试,第四轮读日志,第五轮修代码。每一轮都把大量无关输出带进上下文,后面模型就更容易遗忘最初约束。对超过半小时的任务,应该把输出预算当成项目资源管理:当前阶段需要哪些证据,哪些证据已经过期,哪些日志只在文件中保留,不再放进对话。
一种有效做法是阶段性压缩。完成初始探索后,让 Codex 用十行以内总结仓库结构、相关文件和当前假设;完成失败定位后,用一段总结复现命令和失败原因;完成修改后,用列表总结改动文件和验证结果。后续阶段引用这些摘要,而不是反复打印旧日志。这样既保留任务记忆,又避免上下文被原始输出拖垮。
另一种做法是把命令输出和决策分离。命令输出只描述事实,决策段落只描述推理。比如“事实:CheckoutSummary.test.tsx 的第 42 行断言 expected 3 received 2;事实:组件中筛选逻辑跳过了 disabled item;推理:测试期望包含 disabled item,但当前业务代码排除了它;下一步:确认产品需求和相邻测试。”这种写法能让人看清模型有没有从证据跳到过度结论。
对多 worker 场景,输出治理更重要。多个 Codex 同时在一个仓库工作时,一个 worker 不应运行全量格式化或全量测试并把巨大日志塞进共享讨论。它应该只读取自己负责范围的文件,保留局部日志,最终报告可复现命令和字符级、文件级变更。这样其它 worker 不会被无关输出干扰,也不会误判你的临时文件是项目变更。
质量标准:什么算一条好命令
一条适合 Codex 的命令应满足五个条件。目标明确,别人看命令就知道它要回答什么。范围明确,路径、包名、测试名、时间窗口都写清楚。输出有上限,不会把无界日志直接塞进上下文。失败可诊断,退出码、错误摘要和下一步线索足够。副作用可预期,不会顺手安装、删除、联网或改远端。
反过来,坏命令通常有共同特征:在仓库根目录无范围搜索短词;直接打印生成文件、lockfile 或压缩后的 JSON;用 find 扫所有目录包括依赖缓存;把 npm install、npm test、git diff、git status 串在一行;用 cat 打印几千行日志;把“修复并验证”写成一个不可分割的 shell。这样的命令也许能跑,但不适合让推理模型消费。
技术负责人可以在 code review 中检查 Codex 的命令记录。如果一个任务的主要证据来自两三条范围清楚的命令,说明 agent 工作方式健康。如果主要证据来自几次巨大输出和模型猜测,说明风险较高。这个判断不依赖模型版本,也不依赖项目语言,是长期稳定的工程标准。
评审问题:看输出链路是否可信
审查一次 Codex 任务时,可以沿着输出链路提问。第一个问题是:原始命令要回答的具体问题是什么?如果命令只是“跑一下看看”,后续判断就会很松。第二个问题是:完整输出存在哪里,是否会被误提交,是否含有敏感内容?第三个问题是:进入对话的是完整输出还是筛选摘要?如果是摘要,筛选条件是什么,是否可能漏掉关键错误?第四个问题是:模型结论是否引用了可见证据,还是跳过证据直接给修复方案?
这些问题能把“我觉得模型说得对”改成“证据链足够支持这个修复”。比如一个测试失败,合格证据至少应包含失败测试名、断言差异、相关堆栈和复现命令。一个构建失败,合格证据至少应包含失败包、失败阶段、错误文本和触发命令。一个类型错误,合格证据至少应包含文件路径、行号、类型期望和实际类型。缺少这些证据时,应要求 Codex 继续读取聚焦片段,而不是直接编辑。
输出链路还要看是否污染了后续上下文。如果早期命令打印了大量无关 warning,模型后续可能反复围绕 warning 解释问题。任务中途可以要求 Codex 压缩上下文:列出当前有效证据,标记已经排除的线索,说明哪些长日志不再需要引用。长任务不是把所有信息无限累积,而是不断丢弃已失效材料。
最后要看命令是否可复现。一个只在 Codex 工具输出里出现、没有完整命令和工作目录的结论,不适合进入团队知识库。好的任务记录应该让另一个开发者在同一仓库重新运行关键命令,看到相同失败或相同通过结果。可复现性是输出治理的最终目标。
延伸阅读
- Codex CLI reference
- Codex sandboxing
- Codex permissions
- Codex rules
- Custom instructions with AGENTS.md
GitHub 原文:16-shell-command-response-truncation.md