故障处理

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、远程端及你的项目仍以实际版本、规则和观察为准。

社区讨论

补充你的经验或问题

评论由 GitHub Discussions 承载;参与讨论需要登录 GitHub。