多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

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

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_LOGwarn根本压不住它等于在 v0.142.0 之前用户几乎没有自助缓解的路径。受影响范围覆盖 v0.142.0 之前的所有版本Linux、macOS 是重灾区日志路径~/.codex/logs_2.sqliteWindows/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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个不加 UTM。你需要先去控制台拿 Key地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。先确认你的 Codex CLI 版本这决定了你走哪条修复路径codex --version # 如果低于 0.142.0优先升级 npm install -g openai/codexlatest升级完再确认一次版本然后检查日志文件现状。这一步很关键因为你要建立修复前的基线数据否则没法做前后对比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 本身没有内置的日志轮转开关但你可以通过系统层面的 logrotateLinux或 newsyslogmacOS来管。Linux 下创建一个 logrotate 配置sudo tee /etc/logrotate.d/codex EOF /home/你的用户名/.codex/logs_2.sqlite { daily rotate 3 size 100M missingok notifempty copytruncate } EOFcopytruncate很关键因为 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 * JJ标志表示 bzip2 压缩归档。配置完手动触发一次验证sudo logrotate -f /etc/logrotate.d/codex # Linux sudo newsyslog -F /etc/newsyslog.d/codex.conf # macOS3.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 typetrigger;效果是彻底阻断新日志写入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_urlAPI Keysk-xxxauth.json 或环境变量Model ID你订阅的模型标识config.toml 的 model设置环境变量也可以优先级更高export TAOTOKEN_API_KEYsk-你的实际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约 96TBTrigger 阻断后接近 0接近 0接近 0升级方案消除约 85% 写入量Trigger 方案直接归零但丢日志。你可以根据自己的容忍度选。再确认一下写入源。Linux 用 iotopmacOS 用 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 用 smartctlsudo smartctl -a /dev/nvme0n1 | grep -i Total_LBAs_Written\|Percentage_UsedWindows 用 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_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 对照检查或者直接在模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 发个请求验证 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_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 把多个模型的调用统一在一个订阅下管理切换后端时不用反复改配置。第三SSD 健康纳入例行检查。smartctl的Percentage_Used和Total_LBAs_Written每月看一次尤其是经常跑长任务的机器。已经造成的损耗无法撤销但至少能让你知道还剩多少余量提前规划换盘。最后说个实操细节如果你同时用 Codex CLI 和 Claude Code两边的日志路径和写入行为不一样建议分别建监控脚本。Claude Code 的接入配置可以参考 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里的说明把 Base URL 和 Key 对齐到同一套这样两边的调用日志能在 TaoToken 侧合并查看排查跨工具的调用问题会快很多。这个 Bug 本身已经被 v0.142.0 修了 85%剩下的靠轮转和监控兜底。真正要带走的是那个习惯对任何长时间后台运行的工具都问一句——它到底在写什么写多少写到哪。
返回列表