news 2026/10/22 11:13:57

Codex CLI日志Bug完整复盘:640TB/年的SSD杀手,三种修复方案自查指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Codex CLI日志Bug完整复盘:640TB/年的SSD杀手,三种修复方案自查指南

1. 从一块被写废的 SSD 说起:Codex CLI 日志 Bug 到底发生了什么

如果你在本地跑 Codex CLI 做长时间 Agent 任务,最近可能刷到过一个让人后背发凉的数字:年化 640TB 写入量。这不是什么压测数据,而是 GitHub Issue #28224 里一位用户实测出来的真实结果——21 天,主 SSD 写入约 37TB,折算下来一年超过 640TB。而一块 1TB 消费级 SSD 的额定 TBW 通常只有 600TB 左右,也就是说,理论上一年就能把一块盘的写入寿命保证耗光。

这个问题的核心不是磁盘性能,也不是你操作姿势不对,而是 Codex CLI 内置的 SQLite 日志收集器(feedback sink)默认跑在全局 TRACE 级别。TRACE 是粒度最细的日志模式,会把每一次 WebSocket 数据包收发、每一次 inotify 文件系统事件、每一条 OpenTelemetry 遥测镜像都写进本地数据库~/.codex/logs_2.sqlite。Issue 里的日志分布数据显示,TRACE 级别占总写入字节的 70.7%,codex_otel.log_only和codex_otel.trace_safe再贡献 25.3%——光过滤这两类就能砍掉约 96% 的写入量。

更麻烦的是,这个 SQLite 收集器完全忽略标准的RUST_LOG环境变量。你按常规手段设RUST_LOG=warn根本压不住它,等于在 v0.142.0 之前,用户几乎没有自助缓解的路径。受影响范围覆盖 v0.142.0 之前的所有版本,Linux、macOS 是重灾区(日志路径~/.codex/logs_2.sqlite),Windows/WSL 同样中招,只是临时方案更少。

我试过在一台旧笔记本上复现,15 秒采样里最大行 ID 从 5,003,347,015 涨到 5,003,383,226,也就是 15 秒插了 3.6 万行,而实际保留行数不变——数据库在疯狂执行"插入→写 WAL→剪枝"的循环,行被删了,但每一次物理写入都已经记在 SSD 的磨损账上。这种"静默损耗"最阴的地方在于:软件崩溃能修,数据丢了能恢复,但 SSD 写入寿命一旦消耗就是不可逆的。你在看到任何症状之前,硬件已经悄悄变旧了。

所以这篇复盘不打算只讲"哦有个 Bug 修了",而是给你三套能直接抄的修复方案、可复制的日志轮转配置、SQLite 写入频率检查命令,以及怎么把 endpoint 和auth.json改到 TaoToken 统一观测调用日志,让这类后台写入行为变得可见、可控。适合所有在本地部署 Codex CLI、跑长任务 Agent 的开发者。

2. 动手前的准备:TaoToken 接入与 Codex CLI 环境确认

在开始修日志之前,先把调用链路理顺。很多人只盯着本地 SQLite 写入,却忽略了 API 侧的调用日志同样是"看不见的写入源"——每次请求的重试、流式分片、错误堆栈,如果散落在各个平台,排查起来非常痛苦。把 Codex CLI 的 endpoint 统一到 TaoToken,能让你在一个地方看到所有模型的调用记录,配合本地日志轮转,才算完整闭环。

TaoToken 的定位很简单:一个 API Key 管理多个主流模型的接入,Codex CLI、Claude Code 这类工具切换后端时不用维护一堆独立账户。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个不加 UTM)。你需要先去控制台拿 Key,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。

先确认你的 Codex CLI 版本,这决定了你走哪条修复路径:

codex --version # 如果低于 0.142.0,优先升级 npm install -g @openai/codex@latest

升级完再确认一次版本,然后检查日志文件现状。这一步很关键,因为你要建立"修复前"的基线数据,否则没法做前后对比:

ls -lh ~/.codex/logs_2.sqlite ~/.codex/logs_2.sqlite-wal ~/.codex/logs_2.sqlite-shm

如果这几个文件加起来超过几百 MB,或者你在不用 Codex 的时候它还在涨,那基本可以确认中招了。接着用stat做 15 秒写入量采样,这是后面验证修复效果的核心手段:

stat -c '%s %n' ~/.codex/logs_2.sqlite sleep 15 stat -c '%s %n' ~/.codex/logs_2.sqlite

两次字节数一减,除以 15,就是当前每秒写入速率。Issue 里报告的约 5MiB/s 就是这么测出来的。把这个数字记下来,等会儿修完再测一次对比。

环境确认阶段还要做一件事:找到 Codex CLI 的配置文件位置。不同版本路径略有差异,常见的是~/.codex/config.toml或~/.codex/config.json,认证信息在~/.codex/auth.json。这三个文件(Base URL、Key、Model ID)是接入任何后端的"三件套",缺一不可。你可以先备份:

cp ~/.codex/config.toml ~/.codex/config.toml.bak 2>/dev/null cp ~/.codex/auth.json ~/.codex/auth.json.bak 2>/dev/null

注意:备份是为了出问题时能快速回滚。改配置前一定先备份,尤其是auth.json,里面存的是认证凭据,改错了会导致 CLI 直接无法启动。

到这里,你手里应该有了:当前版本号、日志文件大小、15 秒写入速率基线、配置文件备份。接下来进入真正的修复环节。

3. 三种修复方案的可复制配置与日志轮转设置

三种方案按推荐度排序:升级是根本解法,SQLite Trigger 是过渡期阻断,符号链接重定向是应急手段。每种我都给出完整命令和验证动作。

3.1 方案一:升级到 v0.142.0+ 并配置日志轮转

升级命令前面给过了,这里重点说升级后的日志轮转配置。因为即使修复了 85% 的写入量,剩下的日志如果不加轮转,长期跑 Agent 依然会累积。Codex CLI 本身没有内置的日志轮转开关,但你可以通过系统层面的 logrotate(Linux)或 newsyslog(macOS)来管。

Linux 下创建一个 logrotate 配置:

sudo tee /etc/logrotate.d/codex <<'EOF' /home/你的用户名/.codex/logs_2.sqlite { daily rotate 3 size 100M missingok notifempty copytruncate } EOF

copytruncate很关键,因为 SQLite 文件被进程持有,直接 move 会导致句柄失效,copytruncate 是先复制再清空原文件,对运行中的进程更友好。macOS 用 newsyslog,在/etc/newsyslog.d/codex.conf里写:

# logfilename [owner:group] mode count size when flags /Users/你的用户名/.codex/logs_2.sqlite 你的用户名:staff 644 3 102400 * J

J标志表示 bzip2 压缩归档。配置完手动触发一次验证:

sudo logrotate -f /etc/logrotate.d/codex # Linux sudo newsyslog -F /etc/newsyslog.d/codex.conf # macOS

3.2 方案二:SQLite Trigger 阻断写入(过渡期)

如果你暂时不能升级(比如团队锁定了版本),用社区用户 @beskay 在 Issue #28224 评论里提供的 Trigger 方案,在数据库层直接拦截 INSERT:

# 先完全退出 Codex CLI pkill -f codex # 创建拦截触发器 sqlite3 ~/.codex/logs_2.sqlite "CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;" # 验证触发器已生效 sqlite3 ~/.codex/logs_2.sqlite "SELECT name FROM sqlite_master WHERE type='trigger';"

效果是彻底阻断新日志写入,Codex 本身照常运行,代价是丢失后续诊断信息。适合升级前的过渡期。想恢复的话:

sqlite3 ~/.codex/logs_2.sqlite "DROP TRIGGER IF EXISTS block_log_inserts;"

3.3 方案三:符号链接重定向到内存(Linux/macOS)

把日志文件指到/tmp,写入走内存,重启自动清空:

pkill -f codex mv ~/.codex/logs_2.sqlite ~/.codex/logs_2.sqlite.bak ln -s /tmp/codex_logs.sqlite ~/.codex/logs_2.sqlite ls -l ~/.codex/logs_2.sqlite # 确认指向 /tmp

注意:/tmp下的文件重启后会消失,符号链接需要重建。这个方案只影响诊断日志,不涉及对话内容。Windows/WSL 暂无等效方案,建议优先升级。

3.4 把 endpoint 和 auth.json 改到 TaoToken

这一步是让调用日志统一可观测。编辑~/.codex/config.toml,把 Base URL 指向 TaoToken:

# ~/.codex/config.toml model = "你的模型ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"

然后编辑~/.codex/auth.json,写入你的 Key:

{ "TAOTOKEN_API_KEY": "sk-你的实际Key", "OPENAI_API_KEY": "sk-你的实际Key" }

三件套对照表:

配置项值位置
Base URLhttps://taotoken.net/apiconfig.toml 的 base_url
API Keysk-xxxauth.json 或环境变量
Model ID你订阅的模型标识config.toml 的 model

设置环境变量也可以,优先级更高:

export TAOTOKEN_API_KEY="sk-你的实际Key"

改完重启 Codex CLI,让它重新加载配置。这样你的调用日志就统一在 TaoToken 侧可见了,配合本地日志轮转,写入源就完全透明了。

4. 验证请求与成功结果:写入量前后对比方法

修完不验证等于没修。这一节给你完整的验证流程,包括 API 连通性验证和本地写入量对比。

先验证 TaoToken 接入是否成功。发一个最小请求:

curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "你的模型ID", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 10 }'

正常返回会是一个 JSON,包含choices数组。如果看到choices里有内容,说明 Base URL、Key、Model ID 三件套都对了。这一步能过,Codex CLI 里的调用基本就没问题。

接着验证本地写入量。用前面记下的基线方法再测一次:

# 修复后 15 秒采样 stat -c '%s %n' ~/.codex/logs_2.sqlite sleep 15 stat -c '%s %n' ~/.codex/logs_2.sqlite

把两次字节数相减除以 15,得到修复后的每秒写入速率。对比表大概长这样:

状态15秒写入量每秒速率年化估算
修复前(TRACE)约 75MB约 5MiB/s约 640TB
升级后(v0.142.0+)约 11MB约 0.75MiB/s约 96TB
Trigger 阻断后接近 0接近 0接近 0

升级方案消除约 85% 写入量,Trigger 方案直接归零但丢日志。你可以根据自己的容忍度选。

再确认一下写入源。Linux 用 iotop,macOS 用 fs_usage:

# Linux:看哪个进程在写 sudo iotop -ao -d 5 # macOS:过滤 codex 相关文件系统调用 sudo fs_usage -w -f filesys | grep codex

如果看到 codex 或 node 进程持续高频写入,说明修复没生效,回去检查版本或 Trigger 是否创建成功。用 lsof 也能直接定位:

lsof ~/.codex/logs_2.sqlite

正常情况应该只有 codex 进程持有句柄,且写入速率已经降下来。

最后检查 SSD 健康状态,评估已经造成的损耗。Linux/macOS 用 smartctl:

sudo smartctl -a /dev/nvme0n1 | grep -i "Total_LBAs_Written\|Percentage_Used"

Windows 用 CrystalDiskInfo 看"总写入量"和"健康状态"。Percentage_Used如果已经超过 20%,说明这块盘被这个 Bug 消耗了不少,后续要更注意。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

改配置的过程中,报错基本集中在几个地方。我按真实遇到的顺序列出来,对照着查。

401 Unauthorized:最常见。原因通常是 Key 没写对、环境变量没生效、或者auth.json里的字段名和config.toml里env_key指定的不一致。检查顺序:先echo $TAOTOKEN_API_KEY看环境变量有没有值,再看auth.json里的键名是不是和env_key完全一致(大小写敏感),最后确认 Key 本身没过期。如果用的是env_key = "TAOTOKEN_API_KEY",那auth.json里就必须有TAOTOKEN_API_KEY这个键。

local proxy failed / connection refused:这个报错通常出现在 Base URL 写错或者网络层拦截。先确认base_url是https://taotoken.net/api,注意结尾不要多加/v1(有些工具会自动拼),也不要少写https。然后直接 curl 测一下连通性,如果 curl 能通但 Codex 报 proxy failed,检查是不是系统代理设置干扰了——把HTTP_PROXY、HTTPS_PROXY临时 unset 再试。

reading choices / choices 字段解析失败:这个报错说明请求发出去了,但返回结构不符合 Codex 预期。常见原因是wire_api配错了。Codex CLI 支持chat和responses两种 wire API,如果你的模型走的是 chat completions 格式,config.toml里必须写wire_api = "chat"。写错会导致它去解析responses格式的字段,自然读不到choices。另外确认返回的 JSON 里确实有choices数组,可以用 curl 单独验证。

OAuth 相关报错 / 登录态失效:如果你之前用 OAuth 登录过官方账号,切到 TaoToken 后旧的 token 可能还在缓存里捣乱。清理一下:

rm -rf ~/.codex/auth.json # 然后重新写入你的 Key

有些版本还会在~/.codex/下存 session 缓存,一并清掉再重启。如果报错里出现token expired或invalid_grant,基本都是旧 OAuth 凭据没清干净。

SQLite Trigger 创建失败 / database is locked:说明 Codex 进程还在跑,数据库被占用。先pkill -f codex完全退出,再执行 Trigger 创建。如果还是 locked,检查有没有残留的 node 子进程:

ps aux | grep -i codex

日志文件不增长但磁盘还在写:可能是 WAL 文件在写。SQLite 的-wal和-shm文件是独立于主库的,检查的时候要一起看:

ls -lh ~/.codex/logs_2.sqlite*

如果主库不涨但-wal在涨,说明写入还在发生,只是还没 checkpoint 到主库。这种情况 Trigger 方案依然有效,因为它拦的是 INSERT 层。

排查完这些,基本能覆盖 90% 的配置问题。剩下的如果还搞不定,去 TaoToken 的接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 对照检查,或者直接在模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 发个请求验证 Key 是否正常。

6. 把调用日志统一起来:长期跑 Agent 的观测习惯

修完这个 Bug,我最大的感受不是"终于不写盘了",而是"原来我对自己机器的后台行为一无所知"。Codex CLI 这个 SQLite 收集器跑了那么久,如果不是有人拿stat去量,根本没人会发现。这暴露了一个更大的问题:当 AI 编程工具从"辅助建议"变成"长时间后台运行的 Agent",后台资源行为的透明度和可控性,和功能本身一样重要。

所以除了修 Bug,更值得建立的是观测习惯。第一,定期量写入。把前面那个 15 秒采样做成脚本,每周跑一次,写入速率异常升高就是预警:

#!/bin/bash # codex_write_check.sh F=~/.codex/logs_2.sqlite A=$(stat -c '%s' $F) sleep 15 B=$(stat -c '%s' $F) echo "15秒写入: $(( (B-A)/1024 )) KB, 速率: $(( (B-A)/15/1024 )) KB/s"

第二,把 API 调用日志集中到一处。这就是为什么建议把 endpoint 改到 TaoToken——本地日志管的是"工具自己写了多少",API 日志管的是"你实际调用了什么、花了多少、有没有异常重试"。两边合起来才是完整的可观测性。如果你长期跑编码 Agent,可以考虑 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,把多个模型的调用统一在一个订阅下管理,切换后端时不用反复改配置。

第三,SSD 健康纳入例行检查。smartctl的Percentage_Used和Total_LBAs_Written每月看一次,尤其是经常跑长任务的机器。已经造成的损耗无法撤销,但至少能让你知道还剩多少余量,提前规划换盘。

最后说个实操细节:如果你同时用 Codex CLI 和 Claude Code,两边的日志路径和写入行为不一样,建议分别建监控脚本。Claude Code 的接入配置可以参考 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 里的说明,把 Base URL 和 Key 对齐到同一套,这样两边的调用日志能在 TaoToken 侧合并查看,排查跨工具的调用问题会快很多。

这个 Bug 本身已经被 v0.142.0 修了 85%,剩下的靠轮转和监控兜底。真正要带走的是那个习惯:对任何"长时间后台运行"的工具,都问一句——它到底在写什么,写多少,写到哪。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 2:51:22

CLASS直接修改工具:字节码补丁与运行期热替换实战

简介&#xff1a;这份资源面向Java开发者、逆向工程学习者与安全研究人员&#xff0c;提供一套可直接操作.class字节码的实用工具&#xff0c;帮助查看、分析并修改Java类文件内部结构&#xff0c;适用于调试、优化、旧项目维护及第三方库研究等场景。压缩包共1254个文件&#…

作者头像 李华
网站建设 2026/10/21 16:55:32

DMAD蒸馏+H3架构+LoRA:轻量化大模型端到端优化实战

1. 项目概述&#xff1a;这不是“调参玄学”&#xff0c;而是一次可复现的轻量化模型优化实战 你有没有遇到过这样的情况&#xff1a;手头有个不错的开源大模型&#xff0c;比如Qwen或Llama系列&#xff0c;想把它部署到本地显存只有12GB的3090上跑推理&#xff0c;结果一加载就…

作者头像 李华
网站建设 2026/10/17 6:09:50

宠物医院信息管理系统设计与实现:Java+SSM+Flask完整方案

做个宠物医院信息管理系统这个题目&#xff0c;我前前后后经手过好几个版本&#xff0c;但真正交付出去能在一个小诊所里稳定跑起来的&#xff0c;靠的从来不是堆页面和CRUD代码&#xff0c;而是对业务链路的透彻理解。这篇文章把我的设计方案、开发思路和踩过的坑完整写一遍&a…

作者头像 李华
网站建设 2026/10/19 13:49:29

RocketMQ消息堆积怎么办?从定位到根治的完整排查思路与实战

“RocketMQ消息堆积了&#xff0c;你怎么处理&#xff1f;”这个问题我至少被问到过三次&#xff0c;也看别人答过无数次。大部分人的第一反应是“加机器”&#xff0c;第二反应是“重启消费者”——这两个答案在简单场景下看着没问题&#xff0c;但真上了线&#xff0c;特别是…

作者头像 李华