---
name: tibo-reset-codex
description: >-
  查询 ChatGPT/Codex 重置公告及多个 Pro 账号的剩余额度、备用 Full reset；复用 Google
  登录逐个核实并恢复网页账号，换算北京时间。区分 Tibo 官宣、未官宣的平台静默重置、banked
  reset 与账户级周期重置。Use when 用户问「什么时候重置」「额度什么时候恢复」「下次全员重置几点」
  「banked reset 到了吗」「Tibo 说了什么」「usage limit when reset」，或说「额度突然回到
  100%」「好像/肯定又重置了」，或问「几个账号都用完了吗」「哪个还满额」。必须用实时产品状态、独立用户实测与公告交叉核验；禁止因
  Tibo Radar 没有新条目就否定已经发生的重置，并须把太平洋时间当场换算为北京时间。
---

# Tibo Reset — ChatGPT/Codex 额度重置速查

## 这是什么

「Tibo reset」= OpenAI Codex/ChatGPT Work 负责人 **Thibault Sottiaux（X: @thsottiaux）**
在 X 上宣布的广域额度重置传统。社区昵称「Lord Tibo」，有第三方追踪站 **Tibo Radar**
（`codex-reset.com`）和「祈祷重置」亚文化。Tibo 官宣没有固定排期；但平台也会在限额
配置切换时**不发 reset 帖而直接重置账户**。所以「没有 Tibo 帖」只证明没有官宣，不能证明
没有重置。

**先分清用户问的是哪种重置**：

| 类型 | 谁触发 | 在哪看 | 性质 |
|---|---|---|---|
| **官宣广域 RESET** | Tibo / OpenAI | 官方 X 原帖；追踪站只作公告索引 | 无固定排期，常用于里程碑或故障补偿 |
| **静默平台重置** | OpenAI 后端或限额配置发布 | 产品 usage 状态 + 同时段多账户第一手实测 + 排除各自正常周期；官方限额变更只作上下文 | 可以没有 reset 帖；未获官方范围声明时只能称「大范围观测到」，不能称「全员」 |
| **BANKED reset** | Tibo 推文 | 官宣：追踪站 API（`type=credits`）；到账确认：ChatGPT 产品内余额 | 一次性「存着随你用」的额度包，**到账后不自动消耗**，由用户在 usage 页手动兑现；官宣 ≠ 人人到账（有过分批延迟）。触发形态含里程碑庆祝与故障补偿——§1 所述 rollout 延迟补偿属后者（按无访问天数累积，可一人多笔；2026-09 GPT-6 Astra 补偿即此形态） |
| **账户级周重置** | 系统按该账户当前用量窗口 | ChatGPT 产品内「Next reset: …」 | 每人时间不同；使用 Full reset 也会改变周重置日期，不从订阅开通日推算 |

## 入口分流

- 每次调用先按[本地预测反馈](references/forecast-feedback.md)读取已有记录；查询获得相关事件
  证据后回填未决预测。只读记录为空时不创建文件；提出新预测时保存窗口、依据与本轮反馈。
  实际抓取外部数据的调用先落 findings 记录，record/review 用 evidence_refs 挂链（schema 与
  规则见该文档「findings：原始读数层」节）。
- 裸调用（没带具体问题，只想知道现在什么情况）→ 组合执行：台账回看 → 公告线 + 故障线（§1）→
  本机落地状态（§2 脚本），按输出合同先给当前重置状态结论，再附下一窗口主判断（走预测路径）
  与台账回填；公告线、故障线与本机扫描的每次实际抓取先落 findings 记录，回填时用
  evidence_refs 挂链（见[预测反馈的 findings 节](references/forecast-feedback.md)）。**§2 之后必须再跑一次实时 banked 查询**（`scripts/query_usage.py`，读法与字段表见
  [账号 SOP](references/account-usage.md)）——§2 的 rollout 快照结构上没有备用重置字段，不跑就
  答不全「现在什么情况」这个最常被问的维度；只取 banked 一个数即可，本条其余部分仍只管重置状态。
  ⚠️ **scan 与 banked 不要 `&&` 串联**：scan_rollouts 无快照时 exit 1，`&&` 会把 banked 查询直接
  短路掉——表面上全套像跑过了，实际少一维（2026-09-19 实测）。两条分开跑，或串联时给 scan
  加 `|| true`。**循环场景下 skill 里的裸命令被 `same-cmd-resend-guard` 拦**（端点抖动失败一次
  后逐字节重发即拦）——命令开头加 `cd <skill 绝对路径> &&` 换结构即放行（2026-09-19 实测）。
  定时循环（如 `/loop`）重复触发时，若距上次检查间隔很短（<10 分钟）且上轮无改判信号，可只跑
  公告线确认无新官宣、跳过 incidents/banked/本机扫描全套——官宣与补偿型重置最小间隔为小时级，
  短间隔内不会漏事件；间隔正常或上轮出现过新信号时仍跑全套。
  **按时段降频**（与按间隔那条正交，两者叠加）：Tibo 睡眠时段（太平洋 1AM–8AM = 北京 16–23）
  只跑公告线一个请求确认无新官宣，跳过 incidents/banked/本机扫描全套——官宣型重置从不落在他
  睡眠时段（历史模式，见「Tibo 的时间写法」节），补偿型不依赖他的作息。**例外**：降频轮里只要
  Radar 冒出带 `official_window`（明确时间点）的预告，立即升级回全套并密集盯到窗口过去。代价
  要说清：睡眠时段万一发生无官宣的静默重置，会延迟到活跃时段才发现——静默重置也是人触发的，
  历史模式支持睡眠时段不会发生，按可接受处理。（2026-09-18 用户拍板，当晚已跑多轮稳定。）
  **长跑循环的覆盖度自审**：连续多轮全绿会诱导越跑越窄——把「这几条腿没报警」误当成「覆盖
  完整」。事件检测和预测是两件事，固定的检查腿只保证「不漏新重置」，不保证「下一次预测有
  独立视角」。固定节奏之外，周期性问一次「我覆盖了哪些判断维度、漏了哪一类信号」；用户问
  「有没有漏信号 / 别人怎么预测」时，就是在要这次自审，走静默重置路径同款取证、别用「全套
  已跑」搪塞。
  点名额度/余额本身的问法整条走账号 SOP，本条只管重置状态。
- 用户问「我们几个账号 / 都用完了吗 / 还有两个满额 / 还有几次 Full reset」→ 先读
  [逐账号额度查询与网页登录恢复](references/account-usage.md)。
- 用户问「Tibo 说了什么」或明确只要官宣 → 查**公告路径**。
- 用户问「下一次是什么时候 / 明天会不会重置 / 值不值得等」，包括上一轮刚重置后的追问 →
  查公告后读[下一轮重置预测](references/next-reset-forecast.md)，给出主判断、依据和更新时间。
  默认继承上文的重置类型；明确问个人周期时仍走账号 SOP，不用个人周重置日期代答全局预测。
- 用户说自己的 weekly/5h 回到 100%、`Next reset` 改了，或贴出 usage 截图 → 先把它记为
  **该账户的直接观测**，再查是个人周期还是跨账户事件。
- 用户明确说「肯定又重置了」且聚合器无记录 → 立即走**静默重置路径**；禁止重复查询同一
  聚合器后再次用空结果驳回用户。
- 用户只问当前剩余或下一次周期重置 → 同样进入账号 SOP 的实时查询；需要解释历史跳变时
  再走 §2 的 rollout 快照。当前余额查询不要求先跑整段历史重建或重查公告。
- 想跳过网页手动登录、把日常 Chrome 已登录的 Google 账号直接接到隔离查询 profile →
  `scripts/launch-usage-profile.sh <a|b>` 建/开隔离 Chrome profile，`scripts/import-google-cookies.py --profile <a|b>`
  搬运 `.google.com` 系 cookie（机制与安全论证见脚本自身 docstring，2026-09-15 验证过）。
- 想**自动**驱动隔离 Chrome 查第二账号用量 → `node scripts/read-usage-profile.cjs`（依赖
  `~/.chrome-profiles/tibo-cdp/` 的 playwright；默认 profile a）。读模式直接解析当前登录账号
  usage；加 `--drive-login` 在未登录时自动点 `Continue with Google` 驱动到 Google 账号选择器
  并列出全部账号。**实测边界（2026-09-16）**：隔离 profile 无 Google 会话 token，账号选择器
  每个账号都标 `Signed out`，免密直登做不到——密码/验证码交人工，首登一次后读模式才全自动。
  踩过的可执行细节（代理、真实输入通道对哪个按钮有效、登录态判据、OAuth 轮询）见
  [account-usage.md 隔离 Chrome 自动化节](references/account-usage.md#隔离-chrome-profile-自动化真实输入通道的适用边界2026-09-16-实测)。

## 输出合同：先给结论，再交代边界

用户原话（2026-08-26）：「**你必须给出结论而不是让我给结论。**」

- 第一段第一句必须给出当前证据支持的**唯一最强结论**，禁止用「可能是 A/B/C、请你再看」
  把分类责任交还用户。
- 不确定性用于**收窄结论的属性**，不是取消结论：
  - 只证实一个账户 → 「该账户已经重置；触发原因与影响范围未核实」。
  - 多账户提前跳变且正常周期解释不了 → 「发生了未官宣的大范围静默重置」。
  - 官方明确 all/every → 「官方确认全员重置」。
- 证据、竞争解释和待核字段放在结论之后。拿不到某账户的 `Next reset` 或 banked 状态时，
  仍先对已知事实下结论，再说明哪一层属性不能确认；禁止以「你检查后自行判断」收尾。
- 后续核验动作只用于证实/证伪这个结论，不得把它写成让用户代替 agent 做判断的选择题。
- **未来时间问题要给预测判断。** 有明确预告时先报换算后的官宣窗口；没有时继续分析近期同类
  事件与当前信号，按[预测路径](references/next-reset-forecast.md)给出有依据的主窗口、信心和
  改判条件。「未官宣 / 没有固定排期 / There is no schedule」只说明公告状态，不能独自结束
  回答。资料不足以支持日期时，给出等待是否值得的判断及缺口，不编日期或精确概率。

## 查证工作流

### 1. 公告路径：Radar 是索引，不是产品状态

用 Tibo Radar JSON API 找最新公开公告（2026-08-26 实测 200）：

```bash
curl -s -m 15 "https://codex-reset.com/api/timeline" | python3 -c "
import json,sys
for e in json.load(sys.stdin)['events'][:5]:
    print(e['announced_at'], '|', e.get('type'), '|', str(e.get('summary'))[:100])
    ow = e.get('official_window')
    if ow: print('   窗口:', ow.get('label'), '=', ow.get('start_at'), '→', ow.get('end_at'), 'UTC')
    print('   url:', e.get('url'))"
```

新条目在前。关键字段：`announced_at`（UTC ISO）、`summary`（可能截断）、`url`（原帖）、
`official_window`、`reset_verification_status`。`type` 是内部小写值：`reset` = 广域
重置公告、`credits` = banked/额度包、`boost`/`promo` = 消耗规则类。

**摘要截断也会藏住预告**：2026-09-08 实测最新条目的 `summary` 只截到开头玩笑，
`official_window=null`、`announcement_state=none`、核验标签为 pending；
[原帖全文](https://x.com/thsottiaux/status/2097043464538264003) 却明确预告所有付费订阅的全局重置，
时间为发帖当日太平洋 18:00 左右。先读全文再判断，不能按索引字段宣布“无新预告”。
当次发帖北京 09-08 03:24、太平洋 09-07 12:24，故预告换算为北京 09-08 09:00；
这是当次预告的换算案例，不是固定排期，也不是已到账证据。

⚠️ **`type` 是 Radar 编辑者打的标签，不是事件性质的机器判定——跨平台互动也会被打上
`reset`。** 2026-09-05 实测：Tibo 回复 Anthropic 的 Lydia Hallie（原帖：「We've just reset
weekly limits for everyone on a Claude Max plan」，Claude 官方学重置传统），只回了句
「Wow, huge, wonder why!」的调侃，Radar 照样给它 `type=reset`。读原帖是唯一消歧手段；
fxtwitter 响应里的 `replying_to`（被回复人）+ `replying_to_status`（被回复帖 id）就是为
这一步准备的字段。

`url` 已在上面命令的输出里（2026-08-31 起直接打印，免去二次查询），拿到后优先读原帖。X 帖正文的制胜通道是 **fxtwitter 公开镜像 API**（2026-08-30 实测：
免登录、直连即可、返回完整 JSON；**完整正文在 `tweet.text` 字段——不是 `full_text`**，该键
不存在、照抄会 KeyError；note_tweet 长文全文也给，8-29 官宣长文实测 2324 字符完整拿到、以
自然结尾收束）：

```bash
curl -sS --max-time 20 "https://api.fxtwitter.com/<user>/status/<status-id>" \
  | python3 -c "import json,sys; t=json.load(sys.stdin)['tweet']; print(t['created_at']); print('reply_to:', t.get('replying_to'), t.get('replying_to_status') or ''); print(t['text'])"
```

备胎与死路（同日实测）：
- `cdn.syndication.twimg.com/tweet-result?id=<id>&lang=en&token=a`（官方端点，200）：**正文截断
  276 字符**、note_tweet 只给 id 不给内容——只能核对开头与元数据，长文拿不到。
- `publish.twitter.com/oembed`：301 落到 `publish.x.com` 后（加 `-L`）返回 200+JSON，但可见
  文本同样截断（~273 字符、以省略号收尾，截断点与 syndication 相同）——**与 syndication
  同一档**，够核对开头与元数据，拿不到 note_tweet 全文。
- ~~Jina Reader~~ **可用但间歇，不作主通道依赖**：匿名访问 x.com 会因他人滥用被**间歇性全局
  封禁**（403，2026-08-30 实测：封禁数小时后解除，解除后匿名仍能拿到帖子正文；错误信息点名
  触发滥用的第三方账号）；本仓 jina key 已 402 余额尽。fxtwitter 优先，Jina 只作它的备用。
- fxtwitter 不返回回复**内容**（`replies` 字段只是数值计数）；帖子下的 Tibo 澄清需要 WebSearch
  找转录源补充。但它返回 `replying_to`（被回复人 handle）与 `replying_to_status`（被回复帖
  id）——**足够判断「这条是不是回复、回复给谁」**，这正是上面 `type` 误标消歧的唯一字段；
  被回复帖本身再用同一条命令取一次即可。
- **落地确认帖的回复链要读；tracker 的条目数不等于事件数**（2026-09-10 实测）：09-08
  「All reset for everyone」官宣帖下，Tibo 回复「You forgot the part where I reset usage
  twice in the middle」——正式落地之前当天已中途全局重置两次，这个口径只存在于回复里。
  codexrunway 把这条回复标成了**第二条独立的** Completed Global reset（Confidence 93%）。
  预告＋中途加码＋落地是一轮事件（归并规则见 next-reset-forecast），读回复用同一条
  fxtwitter 命令；回复帖 id 从 tracker 页面里的 x.com 状态链接提取（2026-09-10 即从
  codexrunway 静态 HTML 中 grep 得到），fxtwitter 自身没有 replies 列表。

`codexlimitwatch.com/codex-reset-history` 与 Radar 都以 Tibo 动态为核心上游，属于**同一来源
家族**，只能互查转录/解析是否一致，不能称为独立双源。**LunarWerx Codex Forecast**
（`codex.lunarwerx.com`）介于两者之间：它的**核验腿**独立（站点自述且 meta description 实测
「checked against OpenAI's own status page」），但**数据上游**仍是公开重置记录（含 Tibo 动态、
社区描述其模型由 Tibo hints 驱动）——所以它适合作「第三方对 Tibo 信号的**解读**交叉验证」
（2026-08-30：对 celebration 帖它独立给出同样的「明天重置」读法，标注 95% confident），
**不能当「独立观测到重置事件」的第二源**，否则就犯了本 skill 警告的同源错误。它是 SPA，
静态抓取只能拿到 meta 与壳，正文内容经 WebSearch 引用。HTML
`codex-reset.com/tibo` 是 SPA，静态内容可能滞后；只作人类视图。

**codexrunway.com**（`www.codexrunway.com`，2026-09-01 实测静态可抓、无需 JS）同属这一家族，
但有两个便宜的附加值：给出**带概率的预测窗口**（实测「≥65% 概率、窗口为 PT 当日全天」），
以及会**主动引用官方故障帖**。预测仍是同族解读，不算独立观测源。
**预测腿：可抓 `www.codexrunway.com/api/status.json`（2026-09-18 起纳入裸调用轮询）**——静态页
之外还有这个 JSON 端点，给三个便宜信号：①`events[]` 里每条 completed 事件带 `confidence`
（09-12 全局重置实测 0.96）；②`Expected next reset` 字段——**它给 `None` 本身就是信息**，连最
激进的第三方预测器在 Tibo 沉默超窗后都不再外推，比自建窗口更该参考；③`monitor.status`
自健康。定位说清：它验证的是「多一个独立预测读数」，**不**验证「读它提高了预测准确率」——
至今没有用它做过 hit/miss 回测的完整周期（台账首次真实预测核验仍待未来调用完成），所以是
「值得进循环的便宜交叉信号」，不是「已证明提升预测质量」。抓取：`curl` 直连该端点，3 次重试
（同其他端点，会间歇抖动）。⚠️ **事件完成判定的字段是 `kind=='reset_completed'`，不是
`status=='completed'`**——按 status 过滤会读到 `completed: 0` 的错答案（2026-09-19 实测）；
可直接复制的过滤：`[e for e in d['events'] if e.get('kind')=='reset_completed']`，再按
`announcedAt` 排序取最新。

**Radar 索引不到官方故障线——这是公告路径的结构性盲区。** Radar 只索引 @thsottiaux，而
ChatGPT/Codex 的故障由 **@ChatGPT** 账号和 **status.openai.com** 发布。Tibo 的重置惯例上有
两个触发（里程碑庆祝、**故障补偿**），漏了故障线就漏掉一半的预测信号。2026-09-01 实测教训：
@ChatGPT 在北京 9/1 02:30 发「ChatGPT Work isn't working right now」，只跑 Tibo 通道的那次
回答完全没看到它，7 小时后才从第三方 tracker 的引用里发现。**每次回答前把故障线一起查。**

```bash
# 官方状态页（2026-09-01 实测 200，返回 Partial System Degradation + 未解决事故）
# 重试是必须的，不是保险：不带重试的裸命令实测会间歇吐 JSONDecodeError 而不是「站点挂了」
for i in 1 2 3; do
  o=$(curl -s -m 20 -A "Mozilla/5.0" "https://status.openai.com/api/v2/summary.json")
  if printf '%s' "$o" | head -c1 | grep -q '{'; then
    printf '%s' "$o" | python3 -c "
import json,sys
d=json.load(sys.stdin); print(d['status']['description'])
for c in d.get('components',[]):
    if c.get('status')!='operational': print(' 异常组件:', c['name'], '→', c['status'])
for i in d.get('incidents',[]): print(' 未解决事故:', i['name'],'|',i['status'],'|',i['created_at'])"
    break
  fi
  echo "  attempt $i 空响应，重试中"; sleep 3
done
```

@ChatGPT 的帖子用 fxtwitter 同一条命令，把 `<user>` 换成 `ChatGPT` 即可。**本节所有外部端点
（Radar、fxtwitter、状态页）都会间歇抖动**——本机走代理时实测同一分钟内 `status.json` 取空
而 `summary.json` 成功、Radar 首跑吐空响应体直接 `JSONDecodeError`、fxtwitter 连续两次
`SSL_ERROR_SYSCALL` 第三次成功（2026-09-07 独立复测）——**失败先重试 2–3 次再判定端点
不可用**，一次失败不构成「站点挂了」。

**⚠️ `summary.json` 只看当前绿不绿，读不到历史故障——必须同时查 `incidents.json`。**
只跑 summary 会漏掉两类事件，都落在「上一轮官宣后无新重置」窗口内，是「补偿型重置／静默重置」
（Tibo 两个触发）的候选触发点：①「补偿型」= Codex/Work 故障（例 09-14 02:46 UTC「Elevated
error rates for Codex and ChatGPT Work」）；②**「静默型」= 平台主动调查意外额度重置**（例
09-09 17:29「Investigating unexpected usage limit resets」，正文 "Some Codex users may be
experiencing unexpected usage limit resets"）。**第②类比①更直接，且名字不含 Codex——只按
Codex/Work 命名 flag 会漏掉它。** 只看当前状态页 = 放弃这两条预测信号。每轮把下面这条和
summary 一起跑：

```bash
# 近期 incident + 完整正文（summary.json 看不到）。flag 命中两类：
#   [补偿型] 名字含 Codex/Work
#   [静默型] 名字或正文含 usage limit / unexpected reset / billed / quota（不依赖 Codex 命名）
for i in 1 2 3; do
  o=$(curl -s -m 20 -A "Mozilla/5.0" "https://status.openai.com/api/v2/incidents.json")
  if printf '%s' "$o" | head -c1 | grep -q '{'; then
    printf '%s' "$o" | python3 -c "
import json,sys,re
d=json.load(sys.stdin)
silent=re.compile(r'usage limit|unexpected.*reset|billed|billing|quota|payment',re.I)
for inc in d.get('incidents',[])[:14]:
    name=inc.get('name','')
    body=' '.join((u.get('body') or '') for u in inc.get('incident_updates',[]))
    f=''
    if 'Codex' in name or 'Work' in name: f+=' [补偿型]'
    if silent.search(name) or silent.search(body): f+=' [静默型-额度]'
    print(f\"{inc.get('created_at','')[:16]} | {inc.get('impact',''):6} | {inc.get('status','')} | {name[:52]}{f}\")"
    break
  fi
  echo "  attempt $i 空响应，重试中"; sleep 3
done
```

命中候选时用 `incident_updates[].body` 读正文再判：含补偿/reset/额度措辞 → 升级为强信号（对照
09-12 的 "reset is also landing by midnight today"）；纯错误率抖动无补偿措辞 → 只记候选。
`impact: none` 且 <2h 恢复、正文无额度语义的按噪声忽略。

**③ 社区 monitor —— 静默重置下唯一的独立第二眼，纳入常规轮询。** Radar 只索引 @thsottiaux、
incidents 是 OpenAI 自述；两者都空时，社区实测是能独立发现"静默重置已发生"的通道。每轮和上面
一起跑（两站同源家族，只作交叉不增独立计数；verdict=No 是正常态，只有变 Yes 才触发静默路径）：

```bash
# 社区 reset monitor（独立第二眼，判 verdict）。http!=200 就跳过，不阻塞。
for u in "https://hascodexratelimitreset.today" "https://lidless.app/did-codex-reset-today"; do
  f="/tmp/tibo_c_$(echo "$u"|md5).html"
  code=$(curl -sS -m 15 -A "Mozilla/5.0" -o "$f" -w '%{http_code}' -L "$u" 2>/dev/null)
  [ "$code" = "200" ] || { echo "  $u http=$code 跳过"; continue; }
  python3 -c "
import re,html
t=open('$f',encoding='utf-8',errors='replace').read()
txt=re.sub(r'<script.*?</script>|<style.*?</style>','',t,flags=re.S)
txt=re.sub(r'\s+',' ',html.unescape(re.sub(r'<[^>]+>',' ',txt))).strip()
m=re.search(r'(No sign.{0,80}|Verdict: (Yes|No))',txt)
print('  monitor:', (m.group(0) if m else 'verdict 未解析')[:90])"
done
```

`verdict` 变 **Yes**、或 lidless 文案从 "No sign" 变实锤 → 立即走 §3 静默重置路径（用实时 API +
同时段实测交叉，按证据范围命名，不外推全员）。verdict=No / 解析不到 → 正常态，不动。

### 2. 本机取证：Codex rollout 快照 = 可脚本化的第一手账户证据

`~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl` 每轮都写 `rate_limits` 快照。**这比引导
用户去看产品页更强**：可回溯历史、能把归零定位到分钟级区间、不需要 GUI，也不需要让用户替你
看屏幕（2026-09-01 实测：5 天 48748 条快照，重建出 7 次重置的完整时间线）。字段形状：

```json
{"limit_id":"codex","primary":{"used_percent":76.0,"window_minutes":10080,"resets_at":1788753995},
 "secondary":null,"credits":{"has_credits":false,"balance":"0"},"plan_type":"pro"}
```

本节命令针对默认 `~/.codex` 主页；使用自定义 `CODEX_HOME` 时，将本节 auth 与 sessions 路径
统一替换为同一个已授权主页，不能将两个主页的快照混用。
`resets_at` 是 epoch 秒；`window_minutes` 10080 = 周窗口、300 = 5h 窗口。快照中的
`credits` 与备用重置的区别，见[账号 SOP 的字段读法](references/account-usage.md#实时-api只读一个明确账号)。
**要备用重置数量就别在这份快照里找**：该键只有 `balance`/`has_credits`/`unlimited`，
实测 143 万条快照 `has_credits` 恒为 `false`，**它不携带 banked 数量**。这是「换源」，
不是「这次没查到」——直接跑 `scripts/query_usage.py`。

**会给出貌似合理错答案的陷阱（每一条都不报错；1–3 于 2026-09-01 同一次会话里连踩，4 于 2026-09-03 补）**：

1. **`primary` 槽位不固定指向周窗口。** 同期快照里 `primary` 有时是 300（5h）。按
   `window_minutes` 分桶，别假设 `primary` = weekly——混着读会把 5h 窗口的 0% 当成周额度重置。
2. **`limit_id` 有多个桶，其中有恒零的诱饵。** 实测同期存在 `codex`、`premium`、
   `codex_bengalfox`，而 `codex_bengalfox` 的两个窗口**恒为 0%**，混进序列会凭空造出几十次
   「重置」。**先 `limit_id == "codex"` 过滤再做任何判断。**
3. **`resets_at` 每条快照都秒级微漂。** 用「`resets_at` 变了」判重置会得到几百个假阳性；
   判据用 `used_percent` 大幅下降（>20 点）。
4. **目录日期 ≠ 时间戳范围——按 `sessions/<Y>/<M>/<D>/` 数天数会静默少采样。** 跨午夜的
   长 session 把**次日**的时间戳继续写进**前一天**的目录，所以「扫最近 N 个日期目录」拿不全
   最近 N 天。实测同一分钟窗口 `08-28 00:25–00:29`：扫 7 个目录得 9 行，扫 9 个目录得 **67 行**
   ——少掉的正是长 session 交错写入的那些行，**而多账号交错恰好就长这样**。当天决定性的
   `used 0%→82%` 记录就住在前一天的目录里，按目录数天数的版本结构上看不见它。
   **修法：多扫 2 天目录，再按时间戳过滤**（已内置在 `scripts/scan_rollouts.py` 的
   days+2 目录扫描与时间戳裁剪）。

**窗口锚点的形状——注意「干净 +7d」本身不是重置的证据**（2026-09-01 与
2026-09-03 两次实测）：

- **干净 +7d**：新 `resets_at` ≈ 归零时刻 + 窗口长度。这**只说明窗口从归零那刻重新起算**，
  它同时是按钮式重置和「换到另一个有额度的账号」的形状——两者在这个维度上不可分。**单看
  +7d 就叫重置是本节最贵的错误**：2026-09-03 实测的一台机器上，8 天内出现 **8 次**干净 +7d
  （按新锚点去重后；去重前 9 条），按这条读会得出「静默重置 8 次」，而真相是账号轮替。要
  定性必须再过下面的多账号归因检查。
- **锚点回拨**：新锚点被设到**过去**（实测 -12.6h、-23h，后者甚至早于它替换掉的旧锚点）。
  历史上归因为窗口重排 / 限额配置切换，但**在多账号机器上它同样是「切回另一个账号」的
  签名**（那个账号的窗口开得更早）——先排除账号，再谈配置切换。
- **账号切换**：见下面的多账号检查。它可以伪装成上面任何一种。

**并发 session 会让同一次重置输出两条。** 滞后的 session 先报旧值、再各自更新，于是脚本会
打印两条时间相邻、**新锚点相同**的归零记录（实测 08-28 00:26 与 00:27 是同一次）。**按新锚点
去重再数次数**，否则会把 7 次数成 8 次。

⚠️ **别把「并发 session 滞后」当成万能解释——它专门用来掩盖多账号。** 按锚点去重合并的是
**新锚点相同**的两条，机械上碰不到锚点不同的记录；真正的风险在**判断层**：看到两条时间相邻
而数值矛盾的记录，顺手归给「滞后 session」，就会漏掉账号交错的线索。
**`used_percent` 是已用量，正常使用本来就会上升**。短间隔内大幅上升并伴随锚点回拨时，
核对身份、取样间隔、会话来源和限额配置；不能仅凭形状排除其他解释。
2026-09-03 实测的 `used 0%→82%`（锚点 09-04 → 09-03）是当时检查账号轮替的重要线索。

它**不在归零列表里**，而在脚本第 1 步的「回跳」输出里（两段是不同的代码路径）——去归零输出
里找它一定找不到。所以：**先读第 1 步的回跳行，再去数第 2 步的归零次数。**

另注：脚本本身**不做去重**，第 2 步会把重复的两条都打印出来（锚点相同即可辨认）；
「按新锚点去重再数次数」是人工步骤。

#### 多账号归因检查

本节采用的 rollout 快照**不记 `account_id`**，仅凭这些无身份快照不能区分账号交错与真实重置。

**先说清楚一个结构性陷阱：直觉上的那个检查永远返回「只有一个」。** `~/.codex/auth.json`
只保存**当前登录的那一个**账号，切走的账号不留痕；`~/.cc-switch/cc-switch.db` 只记它自己
管过的条目，手工 `codex login` 换的账号它完全看不见。**拿这两处的当前状态去回答「历史上
用过几个账号」，是用当前快照回答历史问题——它不会报错，只会给一个貌似合理的错答案。**
（2026-09-03 实测：一台确有两个 Pro 账号在轮替的机器，这两个探针都报「只有一个」，导致整份
归因写反。）

按下面 A/B/C 检查收集线索，再按 §3 的证据范围命名。身份不一致证明机器用过多个账号；
要判断某次跳变的原因，仍需把前后读数绑定到具体身份。

没有发现异常也**不能证明单账户**：A 只保留最近刷新时刻，B 不覆盖手工登录，C 看不到锚点
恰好单调的账号轮替。若用户尚未说明该时段是否切号，且答案会改变历史归因，再问一次；
已经确认的事实不重复问。身份无法分离时报告混合记录的归因限制，不把检查全绿当证明。

**执行顺序：先跑本节最后那段重建脚本**，取得归零区间与 C 的回跳；再读 A，只有用户确实
使用 cc-switch 才读 B。A 的刷新时刻需要与归零区间对齐。

**A 层 —— 当前身份 +（指示性的）最后一次刷新时刻**

分别读取身份与刷新时刻，两者证据强度不同：

1. **当前登录身份**（`id_token` 解出的 email / `chatgpt_account_id` / plan）——这是硬事实，
   在 B 层适用时作身份比对。
2. `last_refresh` 时刻——**指示性，不是判决**：
   - **语义未标定**：字段名就叫 refresh，token 续期也会写它。实测该机 `last_refresh` 与
     `id_token` 的 `iat` 同刻、`exp` 恰好 +3600s，**与一次纯 token 续期无法区分**。所以
     「落在归零区间内」不能单独定案；归因条件见 §3。
   - **覆盖面只有一个点**：它是单个时间戳，最多解释**一个**归零区间。本机 7 天窗口内有
     10 个归零事件，A 层对其余 9 个**什么都没说**。
   - **不落在区间内 ≠ 该层干净**：只说明「最近一次刷新不在这个区间」，更早的切换早已被
     覆盖掉（这正是 `auth.json` 只存当前状态的后果）。

```bash
python3 -c "
import json,os,base64
d=json.load(open(os.path.expanduser('~/.codex/auth.json')))
t=d.get('tokens') or {}
print('last_refresh:', d.get('last_refresh'), ' <-- 与归零区间对齐')
print('account_id  :', t.get('account_id'))
idt=t.get('id_token')
if idt:
    p=idt.split('.')[1]; p+='='*(-len(p)%4)
    pl=json.loads(base64.urlsafe_b64decode(p))
    a=pl.get('https://api.openai.com/auth') or {}
    print('email       :', pl.get('email'))
    print('plan_type   :', a.get('chatgpt_plan_type'))
"
```

**B 层 —— 这台机器上还存过哪些账号（仅在用户确实使用 cc-switch 时检查）**

这是历史归因的可选线索，不是账号盘点入口。用户明确不用 cc-switch 就跳过本层，转官网和
Google 已登录账号。（逐账号额度查询的入口裁定见 account-usage：这批账号 2026-09-08 已
裁定不用 CC Switch 管理——B 层的 providers 记录只作邮箱线索，不为查询额度重开此裁定。）
下面的 `providers` 只证明其记录里出现过的身份，不能证明账号清单完整。

`profiles` 表实测可能是空的（2026-09-03 该机 0 行），账号存在 `providers` 里；每条的
`settings_config` 内嵌一个完整 `auth` 对象，解它的 `id_token` 才能拿到身份。

```bash
python3 -c "
import sqlite3,os,json,base64
db=os.path.expanduser('~/.cc-switch/cc-switch.db')
if not os.path.exists(db): raise SystemExit('no cc-switch.db (不构成单账户证据)')
c=sqlite3.connect('file:'+db+'?mode=ro',uri=True)
for pid,name,cur,cfg in c.execute(\"select id,name,is_current,settings_config from providers where app_type='codex'\"):
    auth=(json.loads(cfg).get('auth') or {}); tk=auth.get('tokens') or {}; email=None
    if tk.get('id_token'):
        p=tk['id_token'].split('.')[1]; p+='='*(-len(p)%4)
        email=json.loads(base64.urlsafe_b64decode(p)).get('email')
    mode=auth.get('auth_mode')
    kind='ChatGPT 账号(计入)' if mode=='chatgpt' and email else 'API-key provider(不是账号,忽略)'
    print(f'{name!r} is_current={cur} auth_mode={mode} email={email}  <- {kind}')
"
```

判读规则（**只数真正的 ChatGPT 订阅账号**）：`app_type='codex'` 底下混着 API-key provider
（实测该机有一条 DeepSeek，`auth_mode=None`、`email=None`）——那不是 ChatGPT 账号，**不参与
账号计数**。只看 `auth_mode='chatgpt'` 且 email 非空的行。

- 这类行里出现**与 A 层不同的 email** → 两个账号的直接证据（2026-09-03 与 2026-09-04 两次
  实测都是这样命中的）。
- 只有一行且与 A 层一致 → 该层无阳性证据。
- `email=None` 或 `auth_mode` 非 `chatgpt` 的行 → **忽略**，别拿它跟 A 层比「不一致」。

⚠️ **`is_current=1` 不是「当前登录」的判据。** 它只表示 cc-switch 自己最后切换到谁；用户绕过
它手工 `codex login` 之后这个标记不会更新。2026-09-04 实测：该机 is_current 指向的
email 与 A 层 `auth.json` 显示的实际登录 email 是**两个不同的真实账号**——同一份
读数同时演示了这条陷阱和 B 层的命中形态（两处 email 不一致本身就是多账号的直接证据）。
**当前 CLI 登录身份以 A 层为准；网页身份另从网页核对。is_current 只回答旧工具记录过谁。**

B 层安静**不代表单账户**——它看不见手工 `codex login`。

**C 层 —— 行为签名：锚点回跳（只靠 rollout，A/B 都失效时仍然有效）**

回跳检查不依赖 auth 文件，但它只检测快照形状，不输出账号身份。窗口配置、取样间隔与
账号交错都需要核对；判读：

- `回跳次数 = 0` → 没有阳性证据（**不等于单账户**，见本节开头的闸门说明）
- 回跳带 **used% 上升** → 标记需核对的交错/配置线索，不直接写成多账号事实。
- **大幅**回跳（实测 10.9h、18.7h）→ 优先查账号切换与限额配置变化，归因条件见 §3。
- 回跳存在但**既不大幅、也没有 used% 上升**（中间带）→ 保留待核，不忽略，也不直接定性。
  这一带里限额配置切换与账号切换真的不可分；不要因为「看起来不够大」就默默放行。
  另外注意：`clean` 判定用的是 600 秒容差，而相邻快照间隔实测可达 9.65h——**取样稀疏本身
  就会把一次干净重置误标成锚点回拨**，所以单凭一个中间带回跳不足以下多账号结论。

- ❌ **「同分钟 + 同锚点 + 不同 used%」不是账号交错检测器**（2026-09-16 实测否决，这条路已堵，
  别再花一轮去试）：全量快照里这种组合有 **12924 处**，绝大多数是并发 session 的相邻整数
  （`6.0/7.0`、`7.0/8.0`）的轮询滞后，且**多数只涉及 1 个 session**——