上游关联
- 上游 Issue: openai/codex#9688 — "Codex fails append to a file if code is large"
- 上游状态: 🟡 OPEN(2026-01-22 报告,至今未修复)
- 上游确认根因(etraut-openai):
"It's because we're passing the full patch to the tool, and this overflows the length limit imposed by CreateProcess on Windows."
问题描述
当 Codex 一次性生成较大的 patch(通常几百行以上)时,在 Windows 上会触发以下错误:
✘ Failed to apply patch
└─ execution error: Io(Os { code: 206, kind: InvalidFilename, message: "文件名或扩展名太长。" })
触发条件(可 100% 复现):
- 单次 patch 内容 > ~30,000 字符(Windows
CreateProcess 命令行长度限制)
- 大规模重构:一次性写入多个大文件
- Codex 生成 1000+ 行代码
根因定位(已确认)
ApplyPatchRuntime::build_command_spec()
└─ args: vec![CODEX_CORE_APPLY_PATCH_ARG1, req.action.patch.clone()]
└─ tokio::process::Command::args()
└─ Windows: CreateProcessAsUserW(cmdline_str.join(" "))
└─ Windows 限制: 32,767 字符
└─ ERROR_FILENAME_EXCED_RANGE (code 206)
关键文件:
codex-rs/core/src/tools/runtimes/apply_patch.rs:65-98 — 把 patch 塞进 argv
codex-rs/windows-sandbox-rs/src/process.rs:72-77 — CreateProcessAsUserW 拼接命令行
codex-rs/apply-patch/src/standalone_executable.rs:11-40 — stdin fallback 存在但从未被使用
解决思路
核心设计原则
Agent 无感:在 Codex Agent 层表现为一次性写入大文件,实际执行时分批写入,Agent 不知道底层做了分片。
实现方案
架构设计
ApplyPatchHandler
└─ ApplyPatchRuntime
└─ [新增] ChunkedApplyPatchRuntime (wraps ApplyPatchRuntime)
├─ 检测 patch 大小是否超过阈值
├─ [超过阈值] → PatchChunker.split() → 分批执行
└─ [未超阈值] → 直接透传到 ApplyPatchRuntime
新增模块:PatchChunker
文件位置:codex-rs/core/src/tools/runtimes/patch_chunker.rs(新文件)
核心数据结构:
/// Patch 分片策略配置
const CHUNK_THRESHOLD_CHARS: usize = 20_000; // 每批字符数上限,留安全余量
const CONTEXT_LINES: usize = 3; // 边界保留的上下文行数
/// 分割后的子 Patch 片段
pub struct Chunk {
pub patch: String, // 可独立执行的 patch 文本
pub file_count: usize, // 涉及的文件数量
pub is_first: bool, // 是否为首批
pub is_last: bool, // 是否为最后一批
}
分割策略
策略 1:AddFile(新文件)的分割
对于大文件 AddFile,分割为:
| 批次 |
Patch 操作 |
关键机制 |
| 首批 |
AddFile: <path> + 首批内容 |
创建文件 |
| 后继批 |
UpdateFile: <path> + @@ End of File + 追加内容 |
使用 is_end_of_file: true 追加到文件末尾 |
具体示例:500 行的新文件 src/app.py
# Patch 1(首批)
*** Begin Patch
*** Add File: src/app.py
+line1
+line2
+...(前200行)
*** End Patch
# Patch 2(追加)
*** Begin Patch
*** Update File: src/app.py
@@ End of File
+line201
+line202
+...(中间200行)
*** End of File
*** End Patch
# Patch 3(追加)
*** Begin Patch
*** Update File: src/app.py
@@ End of File
+line401
+line402
+...(最后100行)
*** End of File
*** End Patch
策略 2:UpdateFile(更新现有文件)的分割
对于大文件的 UpdateFile,按以下原则分割:
- 保持
UpdateFile 整体性:单个文件的 UpdateFile 不拆分到不同 Patch
- 多文件时按 Patch 分割:在文件级别分割,确保每个子 patch 不超过阈值
- 保留上下文行:在
@@ 行处分割,保证后续 patch 仍能找到正确位置
具体示例:3 个文件的 Patch,其中 large.py 很大
# Patch 1(首批)
*** Begin Patch
*** Add File: small.py
+small content
*** End Patch
# Patch 2(large.py 的首批)
*** Begin Patch
*** Update File: large.py
@@ class Foo
old_line
+new_line
+...(大文件主体内容)
*** End Patch
# Patch 3(large.py 的追加 + another.py)
*** Begin Patch
*** Update File: large.py
@@ End of File
+more_large_content
+...
*** End Patch
*** Add File: another.py
+content
*** End Patch
策略 3:DeleteFile(删除文件)的处理
DeleteFile 操作本身很小,不会触发阈值
- 如果单个
DeleteFile 导致 patch 超限(极端情况),直接跳过分割,正常执行
执行层:分批执行
文件位置:codex-rs/core/src/tools/runtimes/patch_chunker.rs
pub struct ChunkedApplyPatchRuntime {
inner: ApplyPatchRuntime,
threshold: usize,
}
impl ChunkedApplyPatchRuntime {
async fn run_chunked(
&mut self,
req: &ApplyPatchRequest,
attempt: &SandboxAttempt<'_>,
ctx: &ToolCtx,
) -> Result<ExecToolCallOutput, ToolError> {
let chunks = PatchChunker::split(req, self.threshold);
if chunks.len() == 1 {
// 单批,直接执行
return self.inner.run(req, attempt, ctx).await;
}
let mut outputs = Vec::with_capacity(chunks.len());
for (i, chunk) in chunks.iter().enumerate() {
// 构建当前批次的 ApplyPatchRequest
let chunk_req = req.with_patch(chunk.patch.clone());
// 执行
let result = self.inner.run(&chunk_req, attempt, ctx).await;
match result {
Ok(out) => outputs.push(out),
Err(e) => {
// 记录已成功执行的批次
return Err(ToolError::ChunkedApplyPatchFailed {
failed_chunk_index: i,
total_chunks: chunks.len(),
partial_outputs: outputs,
cause: Box::new(e),
});
}
}
}
// 合并所有输出
Ok(ExecToolCallOutput::merge(outputs))
}
}
错误处理
#[derive(Error, Debug)]
pub enum ChunkedApplyPatchError {
#[error("Patch 被分为 {total_chunks} 批,执行到第 {failed_chunk_index} 批时失败")]
PartialFailure {
failed_chunk_index: usize,
total_chunks: usize,
chunk_summary: String, // 例如 "批1: 修改 src/app.py, 批2: 新增 large.py + small.py"
},
}
ToolCtx 输出
当分批执行时,输出合并为:
Success. Updated the following files:
A src/app.py # 来自批1
M src/app.py # 来自批2
A src/large.py # 来自批2
M src/large.py # 来自批3
A src/another.py # 来自批3
[已分 3 批透明执行]
实现步骤
Phase 1: 核心模块
Phase 2: 集成
Phase 3: 测试
Phase 4: 配置与文档
关键文件清单
| 操作 |
文件 |
| 新增 |
codex-rs/core/src/tools/runtimes/patch_chunker.rs |
| 修改 |
codex-rs/core/src/tools/runtimes/mod.rs — 导出新模块 |
| 修改 |
codex-rs/core/src/tools/handlers/apply_patch.rs — 换用 ChunkedRuntime |
| 修改 |
codex-rs/core/src/tools/handlers/apply_patch.rs — intercept_apply_patch 同步更新 |
| 修改 |
codex-rs/apply-patch/src/lib.rs — 可选:增强 ApplyPatchAction::with_patch() |
上游关联 Issue
| # |
标题 |
状态 |
| #9688 |
Codex fails append to a file if code is large |
OPEN |
| #15003 |
apply_patch fails for large patches (argv transport) |
OPEN |
| #17107 |
CreateProcessAsUserW failed: 206 (~100KB patches) |
OPEN |
| #17259 |
Content ~1500+ chars hits command-length limit |
OPEN |
| #11099 |
Writing large C++ file triggered command-line limit |
CLOSED |
Definition of Done
上游关联
问题描述
当 Codex 一次性生成较大的 patch(通常几百行以上)时,在 Windows 上会触发以下错误:
触发条件(可 100% 复现):
CreateProcess命令行长度限制)根因定位(已确认)
关键文件:
codex-rs/core/src/tools/runtimes/apply_patch.rs:65-98— 把 patch 塞进 argvcodex-rs/windows-sandbox-rs/src/process.rs:72-77—CreateProcessAsUserW拼接命令行codex-rs/apply-patch/src/standalone_executable.rs:11-40— stdin fallback 存在但从未被使用解决思路
核心设计原则
实现方案
架构设计
新增模块:
PatchChunker文件位置:
codex-rs/core/src/tools/runtimes/patch_chunker.rs(新文件)核心数据结构:
分割策略
策略 1:
AddFile(新文件)的分割对于大文件
AddFile,分割为:AddFile: <path>+ 首批内容UpdateFile: <path>+@@ End of File+ 追加内容is_end_of_file: true追加到文件末尾具体示例:500 行的新文件
src/app.py策略 2:
UpdateFile(更新现有文件)的分割对于大文件的
UpdateFile,按以下原则分割:UpdateFile整体性:单个文件的UpdateFile不拆分到不同 Patch@@行处分割,保证后续 patch 仍能找到正确位置具体示例:3 个文件的 Patch,其中
large.py很大策略 3:
DeleteFile(删除文件)的处理DeleteFile操作本身很小,不会触发阈值DeleteFile导致 patch 超限(极端情况),直接跳过分割,正常执行执行层:分批执行
文件位置:
codex-rs/core/src/tools/runtimes/patch_chunker.rs错误处理
ToolCtx 输出
当分批执行时,输出合并为:
实现步骤
Phase 1: 核心模块
codex-rs/core/src/tools/runtimes/patch_chunker.rsPatchChunker::split()分割逻辑ChunkedApplyPatchRuntime包装器ApplyPatchRequest::with_patch()辅助方法Phase 2: 集成
ApplyPatchHandler使用ChunkedApplyPatchRuntime替代ApplyPatchRuntimeintercept_apply_patch()中同步更新Phase 3: 测试
PatchChunker::split()覆盖各类分割场景Phase 4: 配置与文档
CHUNK_THRESHOLD暴露为可配置项AGENTS.md记录此行为关键文件清单
codex-rs/core/src/tools/runtimes/patch_chunker.rscodex-rs/core/src/tools/runtimes/mod.rs— 导出新模块codex-rs/core/src/tools/handlers/apply_patch.rs— 换用 ChunkedRuntimecodex-rs/core/src/tools/handlers/apply_patch.rs—intercept_apply_patch同步更新codex-rs/apply-patch/src/lib.rs— 可选:增强ApplyPatchAction::with_patch()上游关联 Issue
Definition of Done
apply_patch分块语法完全兼容