故障处理
Codex 只改一行却整个文件都变了?确认改动范围、换行与格式化来源
Codex 只改一处,diff 却显示整份文件变化?先对齐工作区、暂存与分支范围,再排查换行、编码、格式化和生成物。用隔离样例说明忽略空白为何不代表行为不变,并保留原有修改。
只让 Codex 改一个条件,回来却看到整份文件被删除再添加,或者 diff 里夹着大量缩进、引号和换行变化。先不要全部接受,也不要全部还原:大面积差异可能来自比较范围、换行转换、格式化、生成工具,或确实超出需求的修改;同一文件也可能同时有几种原因。
本文帮助你确认“这片差异在比较什么、有哪些成因证据、下一步该核对什么”。不把通用 Git 换行问题认定为 Codex 特有故障,也不把差异面板里的所有改动都归给 Codex。若你要撤销已经确认不需要的部分,请接着看保留原有修改的撤销指南。
先按现象选择下一步
| 看到的情况 | 先检查 | 不能直接推出 |
|---|---|---|
| Codex 面板与终端差异不一样 | 仓库、worktree、所选视图与比较基线 | 某一端一定显示错了 |
| 大部分行看起来没变,却都标红绿 | 原始 diff、行尾和文件编码 | 只是显示问题,没有真实字节变化 |
| 换行过滤后仍有少量差异 | 剩余条件、数值、文本与调用关系 | 剩下的就全是必要修改 |
| 缩进、引号、排序一起变化 | 项目格式化或生成步骤及其运行记录 | 有配置就证明这次运行过 |
| 同一块混有自己原来的修改 | 任务前基线及保全副本 | 可以让 Codex 自动精准撤掉自己的部分 |
1. 先对齐比较范围,保留原始现场
若 Codex、编辑器保存动作或生成任务还在继续写文件,先保留未保存内容,再停止本次会继续改动的操作。不要为了清理 diff 先运行格式化、批量转码、暂存全部或还原全部。
适用条件: 你正在检查已有 Git 仓库,在实际执行任务的目录运行以下读取命令:
git --version
git rev-parse --show-toplevel
git rev-parse HEAD
git --no-optional-locks status --short --untracked-files=all
预期结果: 仓库根目录、当前提交与状态对应本次任务。路径不符时先查worktree 改动定位;没有 HEAD、存在冲突或正在变基时,先确认该状态,不继续套用普通提交的比较方式。不要在非 Git 项目里临时初始化一个仓库,把它当作任务前的基线。
当前官方审查说明明确:桌面审查面板反映仓库状态,可能包含你自己、Codex 和其他未提交的改动。默认的 Unstaged 与 Staged、Commit、Branch、Last turn 不是同一范围;CLI 的 /review 也有不同审查范围。界面名称不能证明每一行的作者。
选一个具体文件。下面的 src/example.py 是占位路径,先替换成已核对的仓库相对路径:
git --no-pager diff --no-ext-diff --no-textconv -- "src/example.py"
git --no-pager diff --cached --no-ext-diff --no-textconv -- "src/example.py"
第一条比较工作区与暂存区;第二条比较暂存区与 HEAD。同一文件可以在两个范围中都有改动。分支或提交视图要先核对界面选定的基准与版本,再用相同对象比较,不能拿普通 git diff 去证明分支差异错误。Git diff 的比较对象
普通 diff 不展示未跟踪文件正文,忽略文件也需按任务另查。--no-ext-diff 与 --no-textconv 避免外部差异展示和文本转换干扰这次观察;它们不关闭 Git 的全部属性或内容规范化机制。
先保留未过滤的 diff 和当前文件副本,再做后续对照。 放在仓库外、已授权的本地位置,别覆盖唯一副本;若还涉及暂存版本、未跟踪文件、worktree 或自己的未保存内容,按撤销指南的保全步骤核对覆盖范围。仅保存 HEAD 哈希或一份 patch,不等于保全了全部原有工作。
2. 用空白过滤缩小观察范围,不把它当作安全证明
适用条件: 已确认比较对象一致,并保留原始观察。以下四条都比较同一个文件的工作区与暂存区;若你查的是 Staged,四条都加上 --cached,不要在对照中换范围。
git --no-pager diff --no-ext-diff --no-textconv -- "src/example.py"
git --no-pager diff --no-ext-diff --no-textconv --ignore-cr-at-eol -- "src/example.py"
git --no-pager diff --no-ext-diff --no-textconv --ignore-space-at-eol -- "src/example.py"
git --no-pager diff --no-ext-diff --no-textconv -w -- "src/example.py"
这几种过滤并不等价:--ignore-cr-at-eol 只忽略行尾 CR;--ignore-space-at-eol 忽略行尾空白;-w 忽略更广的行内空白差异。它们比较文本,不理解程序语义。Git 官方选项定义
| 对照结果 | 能说明什么 | 下一步 |
|---|---|---|
| 过滤前后一样 | 该过滤没有改变本次观察结果 | 查其他差异或原因,不能反推为换行问题 |
| 过滤后变少 | 一部分差异被该规则忽略 | 同时检查被隐藏的部分和仍显示的部分 |
| 原始有差异,过滤后为空 | 该比较没有显示未被过滤的差异 | 仍要核实际内容与行为,不宣告安全 |
| 命令报错或文件未找到 | 对照尚未成功 | 核路径、范围、权限和 Git 支持情况 |
空白可能是功能的一部分。 Python 缩进决定代码块,字符串里的空格可能影响长度、匹配或输出。换行和编码转换也会真实改变文件字节,不能一概称为“只是显示”。看起来更短的 diff,不等于更正确的代码。
隔离反例:diff 为空,输出却从 3 变成 2
下面用两个固定的 Python 小文件演示 -w 的边界。保存为 diff_probe.py,在有 Python 3 与 Git 的环境运行 python3 diff_probe.py;Windows 可按已安装的 Python 启动方式调整命令。脚本只在新建临时目录写入虚构样例,退出后清理,不读取你的项目,也不调用 Codex 模型。
import os
import subprocess
import sys
import tempfile
from pathlib import Path
probe_env = {k: v for k, v in os.environ.items()
if not k.startswith("GIT_")}
probe_env.update(GIT_CONFIG_NOSYSTEM="1",
GIT_CONFIG_GLOBAL=os.devnull,
GIT_ATTR_NOSYSTEM="1")
with tempfile.TemporaryDirectory(prefix="diff-probe-") as folder:
before = Path(folder) / "before.py"
after = Path(folder) / "after.py"
before.write_text('print(len("a b"))\n', encoding="utf-8")
after.write_text('print(len("ab"))\n', encoding="utf-8")
command = ["git", "-c", "core.attributesFile=" + os.devnull,
"--no-pager", "diff", "--no-index",
"--no-ext-diff", "--no-textconv"]
for label, options in [("raw", []), ("ignore_space", ["-w"])]:
result = subprocess.run(command + options + ["--", str(before), str(after)],
cwd=folder, env=probe_env,
capture_output=True, text=True)
print(label, "exit:", result.returncode)
for path in [before, after]:
result = subprocess.run([sys.executable, str(path)], check=True,
capture_output=True, text=True)
print(path.name, "output:", result.stdout.strip())
预期结果: raw 的退出码是 1,ignore_space 是 0;两个程序分别输出 3 和 2。这里 --no-index 的 1 表示文件存在差异,并非比较失败;其他错误状态先查原因,不能把失败或没比较到文件当成“没有差异”。这也不是普通 git diff 在所有用法下都会返回的状态。退出状态说明
这个反例证明的是过滤结果不足以判定行为相同,不代表空白变化一定有害;具体文件仍按语言、内容和需求验证。
3. 核对换行与编码,解释索引和磁盘为什么不同
对同一文件读取换行信息及生效属性:
git ls-files --eol -- "src/example.py"
git check-attr text eol working-tree-encoding filter diff -- "src/example.py"
ls-files --eol 中的 i/ 是索引内容识别结果,w/ 是工作区内容识别结果,attr/ 是相关属性。比如 i/lf w/crlf 不能单独判为错误:项目可以要求索引规范化而工作区使用 CRLF。换行输出含义
check-attr 用来核对该路径的属性,不是格式化器执行记录。unspecified 表示未定义该属性,不能当作已明确关闭;必要时分别核对工作区与暂存版本的属性来源。属性读取说明
若怀疑编码或 BOM 改变,在编辑器中检查当前文件与保全副本的编码、行尾和内容,不先点转换或重新保存。--eol 不是完整编码鉴定器;UTF-16 等文件还可能被当成二进制。text、eol、working-tree-encoding 与自定义 filter 会影响工作区和索引内容,存在过滤器时需结合仓库约定理解 diff,不能把展示结果当作原始磁盘字节。Git 内容转换规则
预期结果: 能说明所观察的换行或编码差异,以及项目是否有对应规则。仅发现规则,还不能证明是 Codex、编辑器还是 Git 在这一次触发了转换。没有任务前副本时,记录无法确认的部分,不凭时间戳给修改者定责。
不要为了减少红色而修改全局 core.autocrlf、运行批量重新规范化、强制文本显示或把所有文件转成 UTF-8。这些动作可能扩大差异;应先弄清目标项目本来要求什么。
4. 查格式化、生成物和任务记录,而不只问模型为什么改
先只读检查仓库约定:格式化配置、编辑器的保存动作、任务调用的脚本、生成目录说明及本次工具记录。可让 Codex 在停止写入的状态下整理:
先不要修改、暂存、还原或格式化文件。
核对当前仓库、worktree、比较范围与基线。
逐项列出本次大范围差异可能来自哪里:
需求中的逻辑修改、必要的调用方联动、格式化、生成物、换行或编码。
每项给出实际文件位置、任务/工具记录或项目规则依据;
没有证据的标为待核,不把配置存在当作本次已运行的证明。
区分已有修改与本轮可确认的修改,说明尚无法分离的部分。
接着自己核对它引用的文件与记录。模型解释、配置文件存在、命令计划和退出 0 都不能单独证明某个格式化器实际改了这个文件。 优先查看本次确实执行的命令、输出、生成目标和前后内容;没有记录而需要复现时,在独立测试副本中按项目现有命令核验,不直接重跑会写入原项目的全量脚本。
| 找到的线索 | 如何继续验证 |
|---|---|
| 大片引号、缩进或排序变化 | 对照格式化规则和实际执行记录,检查是否夹有逻辑修改 |
| 文件属于生成输出 | 核对源输入、生成命令与项目约定;不凭扩展名断言可以删 |
| 条件或接口变更涉及其他文件 | 查调用方、类型或测试是否确实需要同步;范围超出预期时说明依据 |
| 工具输出说成功但文件不符 | 对照同一主机、路径、时点和真实文件,保留错误与未完成项 |
| 无法说明来源或改动目的 | 保留现状,列待核项;不靠自动分类直接接受或还原 |
格式化可能是项目要求,必要联动也可能合理增加文件数。验收要看需求、具体差异与行为证据,而不是要求文件数、行数必须最少。任务说明和限制可以帮助约束行为,但不是一个能保证“永不越界”的开关。
5. 根据证据决定处置,再验证目标行为
把每项记录为“有依据保留”“已确认不需要”或“尚待核实”,不要把“模型已归类”当成证明。保留项说明需求或仓库规则依据,并运行与实际改动相关的测试、类型检查或项目要求的验证;还要确认原来就存在的修改仍被保留。
已确认不需要的改动,只有在归属和基线明确时才按安全撤销指南处理;本页不提供整文件覆盖或清空工作区的快捷修复。同一差异块混有双方修改、没有任务前版本,或还存在未保存内容时,先停止自动还原并保全,不能承诺精准分离。
完成标准: 比较范围一致;大范围变化中已确认的成因有证据,未确认项单列;目标行为通过适用验证;原有修改未被覆盖。仍有无法解释的变更时,交付待核清单而非“全部只是格式化”。若是任务声称完成但产物不符,接着查任务完成与真实成果;若是补丁根本没写入,查apply_patch 失败。
本文实际验证了哪些边界
2026 年 10 月 22 日,在 macOS、Git 2.54.0(Apple Git-157)、Python 3.13.7 的独立临时仓库中,用固定虚构文件验证了八组情况:
- CRLF 转 LF 会改变字节,忽略行尾 CR 后该差异可以消失。
- 换行与数值修改混在一起时,过滤后仍可看到数值差异。
- 字符串空格和 Python 缩进两个反例中,
-w可隐藏会改变输出的差异。 - 普通对照与过滤对照输出相同,不能推出成因是空白。
- 已暂存的原有修改与未暂存的本轮样例变化分别出现在不同范围。
- 受属性规范化的文件可同时显示索引 LF、工作区 CRLF,且普通 diff 为空。
- 未跟踪文件可在 status 中出现,却不进入普通 diff 正文。
上述读取诊断前后,隔离仓库的索引与文件哈希不变;样例目录已清理。没有用用户真实仓库复现,也未调用真实 Codex 模型、逐端操作原生差异面板或验证某句提示词能完全约束修改。官方界面范围与 Git 定义已核对,Windows、远程端及你的项目仍以实际版本、规则和观察为准。