Codex 解决了一个抽象 BUG
今天用 Codex 解决了一个十分抽象的 bug,很有意思,就想记录下来。
背景
我正在开发一个名为 Noi 的 AI 浏览器,它基于 Electron 构建。在使用 Electron 时,如果要在本地存储配置信息,一般会使用 electron-store[1] 来做(支持原子写),或通过 node.js fs 模块自己读写文件。
在 Noi 中需大量高频读写本地文件,所以我基于 fs 模块实现了一个类似于 electron-store 的工具函数 NoiStore。自己实现的原因是因为 electron-store:
- 不支持缓存
- 不推荐大数据量读写
- 不支持根数组,必须为对象格式
- 以及各种自定义...
原子写(atomic write)就是让一次写入“看起来像一瞬完成”:要么整个新内容落盘、对外可见;要么完全不改动,绝不会出现“写了一半”的撕裂文件。这对配置、索引、账本等关键文件很重要——程序崩溃、断电或多进程并发时,原子写能把风险收敛成两种安全状态:用户要么读到旧版本,要么读到新版本,绝读不到半成品,从而避免数据损坏与复杂的恢复流程。
常见做法(单分区同一文件系统)是“三步走”:
- 把完整新内容写到临时文件;
fsync临时文件(确保数据真正到磁盘,而非仅在内存缓存);- 用同目录的
rename把临时文件原子替换为目标文件,并再fsync目录(确保元数据持久化)。因为同目录下的rename在主流系统上是原子的,所以任何用户要么看到旧文件,要么立刻看到新文件,不会读到中间态。数据库会用更高级的变体(如 WAL/日志)实现一系列操作的原子性与可恢复性。
注意:原子写解决的是“写入一致性”,不是“并发协调”。它不等于锁:若多方同时写,还需要版本号/锁/单写入者等策略来避免“最后写入者获胜”的逻辑冲突。原子写的代价是额外 I/O(临时文件 + fsync),但换来的是崩溃一致性与更简单的错误处理。
Bug 描述
在 Noi 的底部有一个工具栏(Toolbar),它支持显示/隐藏。正常表现如下:
Bug 出现后 UI 在状态切换后错位,表现如下:
Bug 修复中
因最近两周新增+修改的代码有点多,导致问题排查难度上升。说白了,就是自己有点懒,想着用 codex 修复一下。因为我明确知道最近修改的代码并未涉及到 Noi View 计算(主进程)以及 UI(渲染进程使用 React),所以初步可以排除布局核心计算和 React 相关代码。状态变更需要通过 NoiStore 来存储,而最近两周更新最多的也是它。所以初步就将问题锁定在 NoiStore 上了。
操作状态会通过 NoiStore 更新到本地,进而影响 UI 状态变化,中间也会涉及 IPC 通信,很多交互穿插起来,让我一时间不知道咋描述(属于嘴跟不上脑子了,知道问题,但不知该如何表述)。于是为了图省事,我直接这样询问 codex(vscode 插件),大意是:
- 两周前 NoiStore.ts 是正常的,最近某次更新导致一些配置读取错误,请查找潜在修改问题,进行修复。可参考 NoiStore.old.ts(其实 codex 可以读取 git log 追踪代码变更,为了省事,我直接在项目中放了一个两周前的代码副本)对 NoiStore.ts 进行修复,并实现之后的优化(这句话是为了保证它在修复问题的同时,不影响之后的更新,但事实证明我想多了,这个问题有点复杂)。
- 可能是读取到了缓存,导致数据不是最新(根据实际测试,感觉是状态不同步导致的)?我也不太确定。你对比两份代码,产生了哪些变更?它们是否会产生副作用?
结果也可想而知,因为我以上两个描述比较抽象宽泛,codex 只给了这类回复,并未得出有效结论:
- 我已经把新版和两周前的 NoiStore 实现对比完了,不过现在的症状还抓不牢——需要知道“读取错误”的具体表现才能对症下药。能再补充一下吗?
- 思考:出现抓不牢,具体表现等描述,意思是缺少关键上下文(问题过于宽泛,让 ai 都茫然了)。
- 缓存刷新逻辑本身与旧版一致...
- 思考:与第一次差不多的结论,还是因为上下文不足。
其实我挺喜欢看 thinking 的,比如下面这张截图,它调用了 git 命令、以及通过 shell 在本地写入测试数据验证函数功能等。
在 ai coding 中,版本管理尤为重要。它不但是你代码的最后防线(ai 搞乱你的代码可以回滚),还可以让 ai 追踪代码的变更,去解决一些特殊问题。codex 在多次编辑代码时,一般会将之前的代码进行暂存(git stash)。
既然上下文不足,那就继续补充呗。于是我进行了第三次提问:
这次的 thinking 就靠谱了很多,我摘取了一些关键截图。在看 thinking 时,当它分析到跨进程状态同步读取比较棘手时,我就感觉这次解决 bug 有戏。
如果以上截图没看明白,我简单解释一下,codex 通过代码检索分析以及 shell 授权写入测试用例准确定位到了问题:
- UI 错位根因:store 实例缓存 + 异步磁盘刷新延迟;NoiWindowStyle 获取配置链路与布局 class/padding 条件需复核。
- 克隆策略风险:cloneShallow 改动可能导致嵌套对象替换/保留异常,影响 watchers 与更新触发。
- 复现实验:脚本比对“缓存读 vs 磁盘新读”,定位陈旧值来源与同步时序问题。
- 跨进程同步:无立即写盘/无 watcher 时渲染进程短暂读旧值;可禁用配置写入去抖或改为传参绕开竞争。
- 立即一致性:配置类 store 将 writeDebounceMs=0;可新增 flushOnWrite 选项细粒度控制。
- 读取陈旧值原因:stat 节流 + watcher 刷新机制耦合;调低 statThrottleMs、使用同步写或强制 flush。
- 配置一致性治理:应用中配置项多由 config store 下发,旧 store 类型文件是否统一更新可权衡;用 ensureConfStore 跑测以防回归。
补充截图:shell 授权执行测试用例(系统权限,需用户手动确认)。
最终在修改 17 行代码后,bug 被修复。
Bug 复盘
截止到 bug 修复,我都还没反应过来是修改了哪些代码才导致此 bug,于是就让 codex 进行复盘。
一语惊醒梦中人,当看到 @electron/remote 时,我恍然大悟。这部分代码正是后来新加的,为了兼容在渲染进程中使用 NoiStore。但它在渲染进程调用时会初始化一个新的 NoiStore 实例(和主进程不同步)。比较巧的是之前为了做缓存性能优化,引入了 writeDebounceMs(默认 32ms)延迟写入,几个因素共同作用,导致状态更新不即时。
结语
之所以想记录这次 bug,是因为自己的前两次提问都很抽象,几乎没有给出有效上下文,但 codex 经过自己的一系列测试分析,仅用三次对话就解决了让我一脸茫然的 bug(NoiStore 的核心代码都是 ai 实现的,我也没有逐行 review)。这可能也是一次小小的警告,完全放手交给 ai,自己也不去理解 ai 实现的代码,出了问题,大概率是一脸懵逼...
codex 分析解决问题能力确实 👍,但 AI 想要高效解决问题,还是需要人的编程经验去辅助(如果我第三次没有缩减范围提问,codex 可能还需要花些功夫才能定位问题)!有点相互成就的意思,你强,ai 则强!
References
electron-store:https://github.com/sindresorhus/electron-store