从新线程卡死到钉钉实时告警:Codex Plus 额度全链路监控实战
从新线程卡死到钉钉实时告警:Codex Plus 额度全链路监控实战
摘要
本文解决两个独立但常见的 Codex Desktop 痛点:
- 每次新建线程都先刷一轮"Reconnecting... 2/5"的原因,以及一行配置的修复方法。
- 从零搭建一套本地 Python 监控脚本,定时读取 Codex 额度数据,支持双窗口阈值告警和钉钉推送;最后扩展到双账号独立监控。
一、新线程 Reconnecting 问题排查
1. 现象复现
打开 Codex Desktop,点击"New Thread",界面上连续出现:
Reconnecting... 2/5
Reconnecting... 3/5
Reconnecting... 4/5
Reconnecting... 5/5
等待一段时间后通常能正常对话,但每次都要经历这段等待,体验很差。
2. 根因定位
查看 Codex 运行日志(~/.codex/sessions 下最新的 .jsonl),会看到两个关键信号:
wss://chatgpt.com/backend-api/codex/responses → tls handshake eof
unhandled responses event: response.function_call_arguments.delta/done
Codex Desktop 新线程启动时有一个 WebSocket 预热流程——它会先尝试连接 wss://chatgpt.com。如果这条 WebSocket 连接握手失败(常见于某些网络环境下 TLS 握手被中断),客户端会连续重试 5 次,这就是你看到 Reconnecting... 2/5 ~ 5/5 的原因。
重试耗尽后 Codex 会降级到 HTTP/SSE 传输继续完成请求,所以最终能用,但启动阶段一直在等。
3. 修复方法
在 ~/.codex/config.toml 里加一行,直接跳过 WebSocket 尝试:
transport = "responses_http"
完整示例(现有配置参考):
model = "gpt-5.4"
model_reasoning_effort = "high"
transport = "responses_http"
改完后重启 Codex Desktop,新线程启动时不再经历 WebSocket 预热,直接走 HTTP/SSE,Reconnecting 提示消失。
二、本地额度监控脚本
Codex Desktop 只在接近限制时才主动提示,平时完全不知道还剩多少额度。下面从零构建一套本地监控,零外部依赖,纯标准库 Python。
1. 数据来源
Codex Desktop 会把每次请求的 token 统计写入本地 session 文件:
~/.codex/sessions/2026/04/16/rollout-*.jsonl
每个 .jsonl 文件里,类型为 event_msg 且 payload.type == "token_count" 的行包含了你需要的额度信息:
{
"type": "event_msg",
"payload": {
"type": "token_count",
"rate_limits": {
"primary": {
"used_percent": 72.3,
"resets_at": 1745000000,
"window_minutes": 300
},
"secondary": {
"used_percent": 18.1,
"resets_at": 1745604800,
"window_minutes": 10080
}
}
}
}
其中:
primary:5 小时窗口secondary:7 天窗口used_percent:已使用比例,100 - used_percent即剩余比例resets_at:Unix 时间戳,下次重置时间
2. 核心解析逻辑
def find_latest_token_count(session_root: Path):
"""扫描所有 session 文件,返回最新一条 token_count 事件。"""
files = sorted(
session_root.rglob("rollout-*.jsonl"),
key=lambda p: p.stat().st_mtime,
reverse=True,
)
for path in files:
with path.open("r", encoding="utf-8") as f:
latest = None
for line in f:
event = json.loads(line)
if (
event.get("type") == "event_msg"
and event.get("payload", {}).get("type") == "token_count"
):
latest = event
if latest:
return latest
return None
3. 阈值状态机
为了避免同一个阈值在一次重置周期内重复发送通知,需要维护一个状态文件记录"已通知过的阈值"。
设计规则:
thresholds.primary:5 小时窗口触发阈值,如[30, 0](剩余 30%、0% 各发一次)thresholds.secondary:7 天窗口触发阈值,如[30, 20, 10, 5, 0]- 每个阈值在当前
resets_at周期内只发一次 - 当
resets_at变化(新周期)时,自动清空已通知记录 - 支持"恢复通知":额度回升超过恢复阈值时发一条"额度已恢复"
// state.json 结构
{
"windows": {
"primary": {
"resets_at": 1745000000,
"notified_thresholds": [30],
"notified_recovery_thresholds": []
},
"secondary": {
"resets_at": 1745604800,
"notified_thresholds": [30, 20],
"notified_recovery_thresholds": []
}
}
}
4. 钉钉通知格式
通知消息示例:
Codex Plus额度提醒
7天 阈值提醒: 剩余 5%
窗口: 7天
剩余: 5.2%
重置时间: 2026-04-17 09:49:31
距离重置: 0天23小时12分钟
关键字段:
- 不带时区缩写(避免
CST出现在 webhook 消息里) - 时间格式统一为
YYYY-MM-DD HH:MM:SS - 距离重置用"天小时分钟"格式,直观
发送代码(标准库 urllib.request,无需 requests):
def send_dingtalk(webhook: str, content: str) -> None:
payload = json.dumps({"msgtype": "text", "text": {"content": content}}).encode("utf-8")
req = urllib.request.Request(
webhook,
data=payload,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=15) as resp:
result = json.loads(resp.read().decode("utf-8"))
if result.get("errcode") != 0:
raise RuntimeError(f"DingTalk error: {result}")
5. 目录结构
~/.codex/codex-usage-monitor/
├── config.json # webhook、阈值、订阅到期时间
├── state.json # 已通知阈值的状态(自动维护)
└── logs/
├── monitor.stdout.log
└── monitor.stderr.log
config.json 示例:
{
"dingtalk_webhook": "(钉钉机器人 webhook 完整地址)",
"poll_interval_minutes": 5,
"message_prefix": "Codex Plus额度提醒",
"thresholds": {
"primary": [30, 0],
"secondary": [30, 20, 10, 5, 0]
},
"notify_on_reset": true
}
6. launchd 定时运行
用 macOS launchd 每 5 分钟拉起一次脚本,取代手动 cron:
<!-- ~/Library/LaunchAgents/com.openai.codex-usage-dingtalk.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.openai.codex-usage-dingtalk</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>/Users/yourname/.codex/tools/codex_usage_dingtalk.py</string>
<string>check</string>
</array>
<key>StartInterval</key>
<integer>300</integer>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/yourname/.codex/codex-usage-monitor/logs/monitor.stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/yourname/.codex/codex-usage-monitor/logs/monitor.stderr.log</string>
</dict>
</plist>
安装:
# 生成并加载(脚本内置 install-launch-agent 命令)
python3 ~/.codex/tools/codex_usage_dingtalk.py install-launch-agent
# 手动加载
launchctl load ~/Library/LaunchAgents/com.openai.codex-usage-dingtalk.plist
# 立即触发一次检查(验证 webhook 是否通)
python3 ~/.codex/tools/codex_usage_dingtalk.py check
三、双账号独立监控
如果你有两个 Codex 账号要分别监控,只要把脚本变成"配置驱动的多实例"——不同账号用不同的 config.json 和 state.json,再各装一个 launchd 任务。
1. 账号 A(本地 session 读取)
适合当前设备登录的账号,直接读 ~/.codex/sessions:
python3 codex_usage_dingtalk.py check \
--config ~/.codex/codex-usage-monitor/config.json \
--state ~/.codex/codex-usage-monitor/state.json
2. 账号 B(手动 Cookie 方式)
如果另一个账号没有在本地 Codex Desktop 登录,可以通过浏览器 Cookie 拉取 ChatGPT 的 usage 数据。
获取 Cookie 的方法:
- 在浏览器打开 chatgpt.com,按
Option + Command + I打开 DevTools - 切到
Network面板 → 刷新页面 - 点任意一个发往
chatgpt.com的请求 → 找Request Headers→ 复制整个Cookie:值
注意:只复制 Cookie: 后面的值,去掉 Max-Age、Domain、Path、Expires、HttpOnly、Secure、SameSite 这些元信息(那些是 Set-Cookie 响应头里的内容,不是请求 Cookie)。
配置方式:
{
"auth_source": "manual_cookie",
"manual_cookie_header": "(从浏览器 DevTools 复制的完整 Cookie 请求头值)",
"account_email": "(填写该账号的登录邮箱,用于校验防止串号)",
"dingtalk_webhook": "(钉钉机器人 webhook 完整地址)",
"message_prefix": "Codex 账号B额度提醒"
}
脚本会先用 cookie 调 /api/auth/session 接口校验当前登录邮箱,与 account_email 精确匹配后再拉取额度,避免串号。
3. 两个 launchd 任务并存
分别安装成不同 Label 的 LaunchAgent,彼此互不干扰:
com.openai.codex-usage-dingtalk ← 账号 A
com.openai.codex-usage-dingtalk-b ← 账号 B(不同 config/state 路径)
小结
以上是两个独立问题的修复总结:
- WebSocket 问题:一行
transport = "responses_http"解决,不需要改应用本体。 - 额度监控:纯标准库 Python 脚本 + launchd,不依赖任何第三方包,可以直接在 Python 3.9+ 上运行;状态文件保证阈值不重复通知,扩展到多账号只需多份配置。

浙公网安备 33010602011771号