资讯动态

如何让Codex先做根因分析再修复Bug?分页错位排查实战

发布时间:2026/9/10 7:01:47 来源:尧图企业网站定制
1. 别急着让Codex动手一次分页错位修复的翻车经过先说个真事。上个月我负责的一个后台管理项目上线后测试那边陆续反馈说列表页的分页错乱了——第一页和第二页的数据有重复第三页偶尔缺一条翻到第五页的时候甚至出现了“上一页”按钮高亮、但实际还在第三页的诡异情况。我第一反应是后端SQL的LIMIT/OFFSET写错了于是直接打开Codex输入了一段包含完整报错信息的描述让它“帮我修一下分页问题”。结果Codex给了我一份看起来非常专业的修复方案调整了后端分页查询的LIMIT和OFFSET计算方式顺手改了两个前端组件里的页码状态管理逻辑把currentPage的初始值从0改成1还在几个关键位置加了Math.max防止页码越界。看着diff我一度很满意但提交上去之后测试妹子直接在群里发了三个问号分页错位不但没修好连第二页的数据排序都乱了。我当时的表情大概跟很多被AI“自信修错”坑过的人一样——世界观破碎。复盘的时候我意识到一个问题我给Codex的任务描述里全是症状没有让它先搞清楚病根。它拿到一个模糊问题后基于大量训练样本里的常见套路去猜而分页错位这种问题根因路径实在太多了——可能是后端SQL参数传递顺序问题可能是ORM层的count和fetch走了两条不同的查询通道可能是前端缓存了上一次请求的响应甚至可能是Redis里缓存了一个过期的分页总量导致前端计算总页数时用的是脏数据。从那次之后我给自己定了一条规矩凡是遇到Codex要改动逻辑类代码的任务先强制它走调试Skill流程让它自己把复现步骤、现象描述、根因结论这三样东西交出来在它没交齐之前不允许进入修复阶段。后来我几乎把这条工作流固化成了团队内部处理疑难Bug的固定动作这篇文章就把这套方法完整拆开讲一遍希望你们别走我那条弯路。2. 为什么分页错位这类Bug特别容易在Codex手里翻车分页错位这四个字听起来很简单它其实是前端、后端、数据存储三层相互叠加后才会浮出水面的问题。我拿最常见的场景举例后端分页公式绝大多数后端代码会这么写——# 伪代码示意常见分页查询 offset (page - 1) * page_size items db.query(...).offset(offset).limit(page_size).all() total db.query(...).count()这个公式在大一统的场景下没有问题数据总量稳定、单条记录不删除、排序字段唯一。可一旦数据在查询过程中处于动态变化状态比如用户正在操作、定时任务在批量修改或者你用了分布式数据库的多个只读副本那COUNT和OFFSET两个操作拿到的数据快照就可能不完全一致。体现在界面上就是总页数变了但当前页数据还是旧排布于是出现“第一页和第二页内容重叠”“中间少一条”之类的视觉错位。前端页码状态React或Vue项目里常见的分页组件一般绑定了当前页码、每页条数、总条数三个状态。如果后端返回了新的总条数而前端没有在响应里正确更新totalPage那么页码渲染就会停留在上一次的页数上。用户点下一页前端把page1传给后端后端老老实实返回了对应页的数据但页码高亮和按钮禁用状态是拿旧totalPage算的表现就是“数据已经到第五页了但分页器还高亮在第三页”。数据一致性问题如果项目里使用了Redis这类缓存来存分页响应缓存键设计得不够严谨比如只用了page和page_size没有带筛选条件的hash值那不同筛选条件下会互相串数据这也能算是一种分页错位。关键在于上面这几类可能性凭报错信息或者肉眼观察是很难一下子定位到具体是哪一层的。Codex在接收到一个没有调试过的模糊问题时它的默认行为是“端到端直接生成一个修复补丁”它会同时改动前端、后端、缓存层看似考虑周全实则是把所有可能的原因都蒙了一遍。这不是Codex蠢而是训练目标决定了它的行为模式它是概率性的文本生成模型优化的目标是“生成的代码在语义上最接近人类可能的修复结果”而不是“经过推理验证后的唯一真解”。在缺乏调试约束的情况下它倾向于把一个“多因一果”的问题用“多管齐下”的方式去修。2.1 LLM直接修Bug的三大隐患我在用了很长时间Codex之后总结了它直接进入修复阶段时会踩的三个坑这三条几乎解释了你看到的绝大多数“AI越修越乱”案例症状和根因会被混淆Codex看到分页错位的几个典型表现数据重复、页脚对不上、按钮状态异常很容易根据训练数据里的“高频修复样本”来反推根因。但高频不等于你的项目里的真因——你的代码可能并没有那个高频样本里的典型写法Codex却强行按通用方案改了一遍。修改范围不可控直接从生成器模式进入修复模式Codex往往会同时改多个文件。我见过它为了修一个页码计算bug把完全不相关的样式文件也顺手格式化了一遍diff里多出了两百多行无关变更评审的人根本没法逐行核对。缺少反证过程人类工程师修bug时会做“假设-验证-排除”先假设是SQL问题那就在数据库里手动跑一次分页查询看结果如果SQL没问题再怀疑前端打开Network面板对比响应数据。但Codex直接给补丁时它没有过程只有结论。你没法让它解释为什么排除其他可能性。这就是为什么我在标题里强调“修分页错位前我让它先走调试Skill”——调试流程的作用是把Codex从“猜答案”拽回“找答案”的路径上。3. 先让Codex把复现和根因交出来——这才是调试Skill的正确用法调试Skill并不是Codex新出的某个神秘功能它本质上是一套通过Project Instructions引入的自定义规则体系让Codex在接收任务后不直接给出修改代码的建议而是先按固定的流程执行复现 → 分析 → 根因 → 修复方案 → 验证策略。用大白话说它就是给Codex戴了一副“先别动手”的紧箍咒。你通过配置文件告诉Codex接到Bug类任务时你首先要去读取相关代码、理清数据流然后基于代码逻辑给出可以手动复现的步骤最后再做根因分析在完成这些之前禁止输出任何实际的代码diff。3.1 Codex调试Skill的配置实操我用的Codex CLI版本是0.50.x调试Skill的落地方式主要是靠AGENTS.md文件配合skill指令目录具体路径在项目的.codex/skills/下。你可以创建一个专门针对调试任务的Skill比如叫debug-triage然后在AGENTS.md里声明调用规则。先看我实际的Skill内部结构.codex/skills/ ├── debug-triage/ │ ├── SKILL.md │ ├── scripts/ │ │ ├── reproduce.sh # 自动复现辅助脚本 │ │ └── inspect_logs.py # 日志分析辅助脚本 │ └── templates/ │ └── root_cause_report.md └── AGENTS.mdSKILL.md的内容我简化一下保留核心指令# Debug Triage Skill ## 使用时机 - 用户报告了任何可观测到的功能异常 - 用户提出修复XX问题但未附带明确的根因分析 - 用户无法提供稳定的复现路径 ## 执行目标 在输出任何修复建议之前必须完成以下三个步骤并按固定格式输出 1. 复现路径 - 给出可以稳定触发该问题的具体操作步骤 - 注明需要准备的数据状态或环境条件 - 如果无法在当前环境复现给出最可能触发问题的假设场景 2. 现象记录 - 描述代码层面的具体表现变量值、接口返回、渲染结果等 - 与正常预期行为做对比列出差异点 3. 根因判断 - 列出至少三种可能的根因假设 - 对每个假设给出验证方法 - 结合代码分析给出最有可能的根因结论注明置信度 ## 强制约束 - 在完成上述三步骤之前禁止输出任何代码修改建议 - 禁止在未阅读相关代码文件的情况下直接给出方案 - 根因分析必须引用具体文件与行号在实际命令行里我通常是这么跟Codex对话的 分析并修复分页错位问题请先走debug-triage流程完成后再开始修复因为AGENTS.md里做了规则绑定Codex会读取并加载debug-triage这个Skill然后按照SKILL.md的流程来跑。3.2 Skill生效后Codex的输出发生了怎样的变化下面这张对比表是我在同一个项目里、同一个分页错位问题上记录的真实差异——左边是没走调试Skill时的回答右边是走了调试Skill之后的回答维度直接修复模式调试Skill模式前置动作直接给出代码diff先读取对应的视图函数、ORM模型、前端分页组件这三个关键文件复现步骤没有给出了很具体的步骤构造10条测试数据→筛选特定分类→翻到第二页→观察数据重复根因数量默认一个列出三个假设逐个排除代码修改范围4个文件1个核心文件最终结论的可解释性低每一步都有依据那次具体的根因结论是后端又一个接口在返回分页数据时total字段统计的是未过滤条件的全量数据但items已经是过滤后的数据了。前端拿这个total去算总页数结果自然比实际页数多翻到后面几页拿到的就是空数据或者数据错位。Codex在Skill里给出的复现步骤也很关键——它让我先用管理员账号创建10条商品记录然后在筛选分类为“电子”的情况下翻到第二页。我一试果然稳定复现。那一刻我才真正理解让AI先把复现和根因交出来不是说它比你聪明而是它在一个强制结构化的推理链路里被迫把“每一步的依据”显式地铺开给你看你作为人类才能做真正的判断。4. 分页错位根因梳理Codex给我的假设排除链路这个环节是整个流程里最值钱的部分。很多文章教你怎么用AI但只是教你把问题丢给它这不叫用AI叫碰运气。真正靠谱的用法是你自己心里有一套根因排查框架然后让Codex去逐条验证、填充细节这样得出的结论才不是空中楼阁。分页错位我在不同类型项目里遇到过很多次画一张脑内地图的话根因基本集中在下面几个层次后端SQL层OFFSET计算有误、ORDER BY字段不唯一比如按时间排序但时间精度只到秒就会出现两条数据时间相同翻页时顺序乱跳、多表JOIN导致结果集膨胀后分页数据在内存里被重新裁切。ORM/服务层分页参数没有从请求里正确解析比如前端传的是1-based page后端按0-based使用或者分页参数在中间件里被覆盖了或者查询接口内部又主动对结果集做了一次filter导致返回数量小于page_size但total没同步更新。前端渲染层组件的受控状态没绑好、key值用的数组下标导致数据更新时组件复用错位、分页器拿到的total和items不是同一次接口响应异步竞态。缓存/数据一致层Redis缓存的分页数据没有随数据变更而失效MySQL主从延迟导致count走主库、items走从库两边数据不一致甚至浏览器端Service Worker缓存了老接口响应。Codex在debug-triage流程里让我给每个假设都标了验证方法。我举几个实际的验证手段验证SQL层直接打印或log出最终执行的SQL粘贴到一个数据库客户端里手动执行对比两次翻页的结果。别嫌土这一招能干掉60%的分页问题。验证前端竞态在Network面板里用Slow 3G模拟慢网速疯狂快速点击下一页观察是不是出现了2个并发请求看响应返回顺序是否倒挂。验证主从数据一致在查询接口里临时强制走主库ORM里.using(default)再翻页试试如果错位消失那就是主从延迟。验证缓存污染把Redis里对应的分页key全部删掉再翻页如果问题消失那就是缓存失效策略设计不合理。我那次的问题最终落到了后端“total统计未过滤”这个方向上Codex给出的排除链路大致是这样的读代码发现分页查询调用了两次第一次查items时带了筛选条件category_idxx第二次查total时漏了同一个过滤条件。这就是根因所在——不是复杂的并发问题也不是SQL写法问题就是复制粘贴时漏了条件一个典型的粗心Bug。Codex的验证依据是把total查询语句手动加上过滤条件后在测试库里手动跑了一遍接口返回的total和实际页数对上了。最终代码修改只动了一个文件把缺失的filter条件补上前端和缓存层完全没碰。回头看看如果没有调试Skill的强制约束Codex很可能像第一次那样乱改一通——它改大的概率是先用“通用修复方案”去改前端页码状态管理因为从表现上看页码高亮不对确实更容易被解释成前端状态问题。但真正的修复却是在后端加一行filter。这就是为什么我一直强调AI输出的可信度取决于它的推理过程是否被结构化约束。Skill的价值不只是流程规范它本质上是在逼模型逐层下钻。4.1 让Codex自己决定要读哪些文件但你要复查它的阅读清单这里我有一个特别想分享的细节Codex在调试Skill模式下会自己决定要优先读取哪些文件。但它的选择未必总是对的所以你要做一道人工防线。我在那次分页问题里实际是故意留了一个“坑”。我一直没告诉Codex问题可能出在某个工具函数里那个函数负责拼接分页查询条件。Codex初轮阅读的文件清单是controllers/product_controller.pymodels/product.pyfrontend/components/Pagination.vue它没有覆盖到utils/query_builder.py而真正漏掉filter条件的地方就在这个工具函数里。它是怎么绕到那边的靠的是我给了它一条提示“你可以在项目里搜索所有引用了total_count这个函数的文件”。然后它发现了那个工具函数并最终把根因锁定在那里。这个案例给我的启发是你不需要帮Codex把每一步都列好但你要有能力检查它选择的推理路径是否覆盖了完整的嫌疑面。如果你看它的阅读清单里全是“常见位置”比如视图层、模型层却没有覆盖工具函数、装饰器、中间件这类“藏玄机”的地方那你就要主动引导它扩大搜索范围。4.2 分页错位问题里的常见复现技巧既然要让Codex交出复现步骤你作为人类也得具备一定的“让Bug稳定现身”的能力。分页错位问题想要稳定复现往往需要刻意构造数据条件我的常用招数有这么几个用边界数据量如果每页显示10条你就构造刚好21条、或19条这样的数据别用几百条的大数据集边界条件下分页公式最容易暴露问题。用相同排序字段值构造5条记录它们的排序字段完全一样比如同一秒创建的记录然后在翻页时观察顺序是否稳定。排序字段不唯一时数据库返回顺序在不同查询间可能不一致这是分页错位的一个高频隐藏原因。混合筛选条件先不带筛选翻几页再带筛选条件翻几页对比total的变化。如果total没变但数据变了十有八九是total的查询漏条件了就像我上面经历的那样。快速连续翻页故意制造前端异步竞态看旧响应覆盖新响应导致的错乱现象。Codex在Skill模式里也能根据代码逻辑推导出类似的数据构造方式。它给出的复现步骤其实就是它从代码里反向推断出来的“制造冲突的条件”。这个方法本身是可以复用的——以后你在手工排除Bug时也可以沿着这个思路去构造复现环境。5. 拿到根因之后修复阶段的三个关键决策点Codex走完调试Skill并交出根因结论之后接下来才轮到真正动手修复。很多人在这一步又容易翻车——根因都水落石出了修复阶段却又开始放飞自我。我在实操中总结出三个必须谨慎的决策点。5.1 决策一只改根因不要顺手重构当Codex锁定根因是“total查询漏了过滤条件”之后它会很自然地开始提供修复方案。但第一个给出的方案里它常常会夹带私货比如它觉得query_builder.py里的函数写得不够“Pythonic”于是顺手把函数签名重构了一下或者把query Product.query改成用select()的SQLAlchemy 2.0风格。这个隐患我在一开始那版翻车修复里就体会过。所以我现在的要求很明确在Skill的输出模板里多加一条规则——修复阶段只能产生最小必要的代码变更任何重构、风格优化、无关变量改名都不允许出现在diff里。实操上我在SKILL.md的修复阶段增加了这样的描述如果根因已定位修复时请遵循最小变更原则 - 只修改与根因直接相关的代码行 - 禁止重构函数结构 - 禁止修改变量命名风格 - 禁止格式化非相关代码区域 - 修改前先输出预期变更影响的范围描述等用户确认后再生成diff加了这条约束之后Codex给的diff肉眼可见地收敛了。固定一个问题只需要一行filterdiff里就真的只有那一行。5.2 决策二让Codex先给验证方案再给修复补丁这是我自己摸索出来的一个顺序调整常规做法是你让Codex先给修复方案你改完再做验证但吃过几次亏之后我把顺序反转了——先让它给出修复方案的验证策略再让它动手生成代码。为什么因为如果Codex没法清晰描述“我怎么验证这个修复有效”那大概率说明它对自己的修复也没有把握。让它先写验证策略本质上是在强制它做一次自我推演提前把修复后的系统行为在脑子里跑一遍。对于分页错位这个问题Codex给出的验证策略一般这么写预期验证步骤 1. 构造10条数据并设置筛选条件为“电子” 2. 请求第二页接口断言返回items数量为0因为电子分类下实际只有7条数据第二页应有数据 3. 断言total值为7而不是10 4. 前端分页器此时应显示总页数为1翻页按钮禁用状态正确 5. 回归不带筛选条件请求分页接口确认total值为10我一看这个验证策略心里大概就有数了它的修复逻辑是否可靠从验证步骤的合理性上就能大致辨别出来。如果验证步骤里写的是“打开页面看看是否正常”这种模糊表述那基本不可信。5.3 决策三补一个自动化回归用例分页类问题有一个特征——它不会只犯一次错。这次是total漏了filter下次就可能是排序列不唯一导致顺序抖动。所以修复完成后我会让Codex顺手补一个自动化回归测试把这次根因固化成用例。举个实际的Pytest样例假设项目用的FastAPI SQLAlchemyasync def test_pagination_total_respects_filter(client, db_session): # 构造数据5条电子分类 5条其他分类 for i in range(5): db_session.add(Product(namefelectronic-{i}, category_id1)) for i in range(5): db_session.add(Product(namefother-{i}, category_id2)) await db_session.commit() # 请求筛选分类1的接口 resp await client.get(/api/products?category_id1page1page_size10) data resp.json() # total必须是5而不是10 assert data[total] 5 assert len(data[items]) 5这个测试用例的价值在于以后不管谁重构了分页查询代码只要total又忘了带filter条件这个用例就会立刻失败。把这步补上才算真正意义上关闭了一个Bug而不是临时修好一个表象。6. 哪些场景最适合让Codex走调试Skill一个经验边界写了这么多最后聊聊这种方法适用和不适用的边界。不是所有Bug都值得让Codex跑一遍完整流程也不是所有问题都能靠调试Skill解决。非常适合的场景有这么几类故障现象与根因之间存在多条可能路径像分页错位、数据重复、偶发报错这类前端后端都可能背锅的模糊Bug让Codex先做完整分析的价值最高因为人类工程师在这种多分支排查里也容易漏项。代码库规模中等Codex能在上下文窗口内读完相关文件如果项目特别大Codex的上下文窗口有限它可能会遗漏一些关键文件导致根因误判。这种情况应该先手动帮它缩小搜索范围告诉它重点看哪些目录。修复方案牵一发动全身比如改动一个工具函数会影响十几个调用方这种时候Codex走完整调试流程把影响范围分析清楚再动手远比直接改安全。新接手一个不熟悉的项目遇到Bug时调试Skill里的强制代码阅读路径反而能帮你快速了解项目的数据流转比你自己到处翻代码效率高。不太适合的场景也有非常明显的语法错误或环境配置问题比如包没装、路径写错、端口被占用这种问题直接描述给Codex让它修就行没必要跑一整套调试流程。问题出在外部服务或基础设施比如云数据库的故障、第三方API的下线、Docker镜像拉取失败这类问题Codex没有足够的信息去复现和定位走了流程也只会给出一些猜测性的结论。需要大量人工业务判断的规则冲突如果错位是业务规则本身有歧义导致的比如“某些商品不参与分页展示”这种规则不明确那Codex再怎么能读代码也推不出正确结论因为需求就不清晰。我自己的判断标准很简单如果这个Bug我作为一个人也需要动手翻两三个文件才能定位那它就值得让Codex走一遍调试Skill如果这个问题我闭着眼就能答出来那就直接让它修别浪费Token。经过这次分页错位的完整排查我现在的固定工作流变成了“报bug → 让Codex走debug-triage交复现和根因 → 人工确认根因方向 → 让它给最小修复补丁和验证方案 → 补回归测试用例 → 提交”。这套流程跑熟了之后Codex真正变成了一个“带推理过程的协作者”而不是那个动不动就甩一个大型diff、改完还让你提心吊胆的自动补丁生成器。最后分享一个小技巧调试Skill的配置不是一次性的你可以把项目里遇到过的几种典型根因模式沉淀到Skill的templates里比如分页根因模板、缓存失效模板、竞态条件模板。下次再遇到相似问题时Codex会直接按模板里的假设清单去逐条验证比从零开始推理要快得多。我自己已经把“分页类问题假设清单”“异步竞态类问题假设清单”都写进了项目的Skill模板里实测下来根因定位时间至少缩短了一半。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价