# Codex Auths Validator

> OpenClaw 的 Codex 认证 JSON 自动化验证技能：每小时清理、ZIP/7z 导入校验、每日 GitHub 学习巡检。只要告诉我 JSON 文件存放目录我就能自动工作；若未提供则默认按 Cli-Proxy-API-Management-Center 源码线索自动探测目录。

- Skill: `lsh160981/codex-auths-validator` (Agent Skill, multi-file: 14 files)
- Install (CLI): `npx skillmds@latest add lsh160981/codex-auths-validator`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lsh160981/codex-auths-validator/raw
- Safety review: pending (external: skill-scanner PASS, skillspector FAIL)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: LSH160981 (https://skillmd.com/u/lsh160981)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/lsh160981/codex-auths-validator

---


# Codex Auths Validator

Validate and clean Codex auth JSON files in a batch.

## Run scope

- Target directories（可配置，适配所有用户）：
  - `auths_dir`：有效且有额度目录（用户提供或自动探测）
  - `auths_no_quota_dir`：有效但无额度/限流目录（默认 `<auths_dir>_no_quota`）
- Target files: `*.json`
- Validation endpoint: `GET https://chatgpt.com/backend-api/wham/usage`
- Required headers:
  - `Authorization: Bearer <access_token>`
  - `Chatgpt-Account-Id: <account_id>`
  - `User-Agent: codex_cli_rs/0.76.0 (Debian 13.0.0; x86_64) WindowsTerminal`

## 自动识别与验证范围（对齐原项目多类型能力）

本 skill 会先自动识别 JSON 所属 provider，再决定验证方式：

- 识别优先级：`type` / `provider` 字段 -> 特征字段推断
- 已覆盖类型（与原项目 `AuthFileType` 对齐）：
  - `qwen` / `kimi` / `gemini` / `gemini-cli` / `aistudio` / `claude` / `codex` / `antigravity` / `iflow` / `vertex`
  - `unknown`（无法明确分类时）

## Deduplication rules（去重规则）

在校验之前，先扫描 `auths_dir` + `auths_no_quota_dir` 所有 JSON，对比 `account_id` 字段：
- 扫描顺序：**先扫 `auths_dir`（有额度）再扫 `auths_no_quota_dir`（无额度）**
- 同一 `account_id` 优先保留有额度的那个；其余重复文件移入 `auths_invalid_dir`，原因记为 `INVALID_DUPLICATE`

此逻辑适用于：
- `hourly-reconcile.mjs`（每小时校验前自动去重）
- `import-archive.mjs`（ZIP/7z 导入后、输出报告前自动去重）

## Pre-flight expiry check + 自动续期（Token Refresh）

在调用远程 API 之前，按三层优先级判断 token 是否过期：

```
优先级（高→低）：
  1. JWT id_token 里的 exp 字段（Base64 decode，最权威）
  2. json.expired 字段
  3. json.last_refresh + 7天（兜底推算，假设 codex token 7天有效期）
  无法判断 → 默认未过期，继续走 API
```

**过期后不直接丢弃**，而是先尝试用 `refresh_token` 续期：

```
POST https://auth0.openai.com/oauth/token
{
  grant_type: "refresh_token",
  client_id: "pdlLIX2Y72MIl2rhLhTE9VV9bN905kBh",
  refresh_token: <json.refresh_token>
}

续期成功 → 写回文件（更新 access_token / expired / last_refresh / id_token），继续 API 校验
refresh_token 失效（null / invalid_grant） → ⚠️ 不直接判 INVALID，继续用原 access_token 走 API 校验
  → API 返回 401 才判定 INVALID_EXPIRED（access_token 可能比 expired 字段标注时间更长存活）
网络/5xx → TRANSIENT_KEEP，原位保留，下次重试
```

此逻辑适用于三个脚本：`hourly-reconcile.mjs`、`import-archive.mjs`、`validate-auths.mjs`。

## Report 文件自动清理（reports 目录）

`hourly-reconcile.mjs` 启动时自动清理旧 report 文件：
- 默认保留最近 **72 个**（对应约 3 天），可通过 `--max-report-files <n>` 覆盖
- 按 mtime 升序排列，删除超出数量的最老文件
- 防止 reports 目录无限膨胀

## Invalid 目录积累警告

当 `auths_invalid` 累积超过 **500 个**文件时，hourly-reconcile 会在报告尾部打印：
```
⚠️ auths_invalid 已积累 N 个文件，建议运行清理命令：rm -rf /path/to/auths_invalid/*
```

## Decision rules

### A) codex 类型（可做远程额度验证）
- 过期检测（三层）→ 尝试 refresh_token 续期 → 续期成功则用新 token；续期失败则用原 token 继续走 API → API 返回 401 才判 `INVALID_EXPIRED`
- `200` 且有额度 -> 放在 `auths_dir`
- `200` 但无额度（`limit_reached=true` 或 window `used_percent>=100`）-> 放在 `auths_no_quota_dir`
- `429`（限流/额度耗尽）-> 放在 `auths_no_quota_dir`
- `401/403` -> 判定无效，移入 `auths_invalid_dir`
- `5xx/timeout/network` -> 临时错误，原位保留（不迁移）

### B) 非 codex 类型（先做结构有效性校验）
- 必要字段满足 -> 结构有效，保留原位（可选后续接入 provider 专用远程验证）
- 必要字段缺失 / 坏 JSON / `._*.json` -> 判定无效，移入 `auths_invalid_dir`

## 统一状态输出（用于给用户解释"为什么无效"）

- `VALID_QUOTA`：有效且有额度
- `VALID_NO_QUOTA`：有效但无额度/被限流
- `INVALID_AUTH`：认证失败（401/403）
- `INVALID_EXPIRED`：token 过期、refresh_token 失效、且 API 也返回 401（三重确认才丢弃）
- `INVALID_JSON`：JSON 格式损坏
- `INVALID_MISSING_FIELDS`：缺少必要字段
- `INVALID_APPLEDOUBLE`：`._*.json` 垃圾文件
- `SCHEMA_VALID_PROVIDER`：非 codex，结构有效（保留）
- `INVALID_DUPLICATE`：account_id 重复，优先保留有额度的，其余移除
- `TRANSIENT_KEEP`：临时错误（网络/5xx/续期失败），原位保留下次重试
- reason=`refreshed`：token 已过期但通过 refresh_token 成功续期并继续校验

Move invalid files into `auths_invalid_dir` (default: `<auths_dir>_invalid`). Do not hard-delete immediately.

Invalid directory location pattern:
- `/home/docker/CLIProxyAPI/auths_invalid/`

Write report file:
- `_validation_report.json`

Bulk cleanup command (after user confirmation):
```bash
rm -rf /home/docker/CLIProxyAPI/auths_invalid/*
```

## 入口原则（项目主标语）

**OpenClaw 的 Codex 认证 JSON 自动化验证技能：每小时清理、ZIP/7z 导入校验、每日 GitHub 学习巡检。**

**只要告诉我 JSON 文件存放的目录，我就能自己工作。**

如果用户不提供目录：
- 默认视为用户可能安装了 `Cli-Proxy-API-Management-Center`。
- 按源项目配置与 Docker 挂载线索自动探测目录（`auth-dir` 优先）。

## 首次安装引导（降低新用户操作）

如果用户第一次使用本 skill，先走"自动发现 + 最少提问"流程：

1. 先自动探测认证目录（不要一上来就问用户）：

```bash
node skills/codex-auths-validator/scripts/discover-auth-dir.mjs
```

2. 优先采用探测结果里的 `recommended` 目录。
3. 若探测不到可靠目录，才询问用户认证目录路径（用户说一个路径就直接支持，不要求固定目录）。
4. 若用户安装了 `Cli-Proxy-API-Management-Center`，优先检查其 `auth-dir` 配置与 Docker 挂载路径。
5. 自动创建无额度目录：`<auth_dir>_no_quota`。
6. 首次引导时用中文给用户明确说明：
   - 你识别到的认证目录
   - 双目录规则（有额度/无额度）
   - 接下来会自动创建的每小时与每日任务

> 目标：尽量少让新用户手动配置，能自动发现就自动发现。

## Scripts mapping（两个脚本分别做什么）

### 1) `scripts/validate-auths.mjs`（一次性人工清理/导入前筛选）

用途：
- 手动全量校验、一次性清理、导入前预检。
- 支持无效文件 `delete` 或 `invalid` 两种模式（`invalid` 表示移动到 `auths_invalid_dir`）。

典型命令：

```bash
node skills/codex-auths-validator/scripts/validate-auths.mjs \
  --dir-quota /home/docker/CLIProxyAPI/auths \
  --dir-no-quota /home/docker/CLIProxyAPI/auths_no_quota \
  --invalid-action invalid \
  --concurrency 40 \
  --timeout-ms 12000
```

`--invalid-action`:
- `delete`：无效文件直接删除
- `invalid`：无效文件移入 `auths_invalid_dir`（默认 `<auths_dir>_invalid`）

### 3) `scripts/hourly-run-and-notify.sh`（系统 crontab 专用，直发 TG）

用途：
- 供系统 crontab 每小时调用，**完全不依赖 OpenClaw cron delivery 机制**。
- 直接运行 `hourly-reconcile.mjs`，默认发**精简摘要**（从 `reports/hourly-reconcile-*.json` 读取关键统计，避免解析文本误判）；若 `auths` 与 `auths_no_quota` 两个目录都为空（以目录内 `*.json` 文件数判定，避免解析输出误判），则只发**极简通知**。
- **详细日志默认不发送**（用户反馈不需要）；仅当脚本异常（exit!=0）才自动附详细日志文件。若确实需要在无效/临时错误时也发，可设置 `SEND_DETAIL=1`。
- 这是最稳定的通知方式：不需要 LLM session，不受 auth-profiles.json 影响。

系统 crontab 条目（只配置一个目录即可运行）：
```bash
0 * * * * AUTH_DIR=/home/docker/CLIProxyAPI/auths CODEX_AUTH_VALIDATOR_SECRETS_FILE=/root/.openclaw/secrets/codex-auths-validator.env bash /root/.openclaw/workspace/skills/codex-auths-validator/scripts/hourly-run-and-notify.sh >> /tmp/codex-auths-cron.log 2>&1
```

不写 `AUTH_DIR` 时默认使用：`/home/docker/CLIProxyAPI/auths`。

安装命令：
```bash
(crontab -l 2>/dev/null | grep -v hourly-run-and-notify; echo "0 * * * * CODEX_AUTH_VALIDATOR_SECRETS_FILE=/root/.openclaw/secrets/codex-auths-validator.env bash /root/.openclaw/workspace/skills/codex-auths-validator/scripts/hourly-run-and-notify.sh >> /tmp/codex-auths-cron.log 2>&1") | crontab -
```

### 5) `scripts/hourly-reconcile.mjs`（每小时定时任务专用，稳定版）

用途：
- 供 cron 每小时自动任务调用。
- 内置并发锁（`/tmp/codex-auths-hourly.lock`）防止重叠执行。
- 临时错误（timeout/network/5xx）保留原位，避免统计抖动。
- 无效 JSON 不直接删除，移动到无效目录（默认 `<auths_dir>_invalid`）并输出原因，提示用户是否删除。

典型命令：

```bash
node skills/codex-auths-validator/scripts/hourly-reconcile.mjs \
  --dir-quota /home/docker/CLIProxyAPI/auths \
  --dir-no-quota /home/docker/CLIProxyAPI/auths_no_quota \
  --dir-invalid /home/docker/CLIProxyAPI/auths_invalid \
  --concurrency 40 \
  --timeout-ms 12000
```

## Output contract

Return a JSON summary including:
- total files
- moved files
- moved AppleDouble count
- kept count
- moved-by-validation count
- reason histogram
- invalid directory path
- moved samples

## Archive import workflow（zip/7z 自动接管）

When user provides `.zip` or `.7z` package:

1. Auto-extract archive to temp workspace.
2. Auto-scan all files; only `*.json` enters validation pipeline.
3. Non-JSON files (code, scripts, docs, binaries, etc.) are ignored and never imported.
4. Validate JSON files with this skill rules.
5. Classify to target folders:
   - valid + has quota -> `auths_dir`
   - valid but no quota / 429 -> `auths_no_quota_dir`
   - invalid -> `auths_invalid_dir`
6. Return summary:
   - total files in archive
   - json files processed
   - non-json files ignored
   - imported to `auths`
   - imported to `auths_no_quota`
   - moved to `auths_invalid`
   - status/reason histogram

Recommended command:

```bash
node skills/codex-auths-validator/scripts/import-archive.mjs \
  --archive <package.zip|package.7z> \
  --dir-quota <auths_dir> \
  --dir-no-quota <auths_no_quota_dir> \
  --dir-invalid <auths_invalid_dir>
```

Suggested command sequence:

```bash
mkdir -p /tmp/codex-auths-import
unzip <package.zip> -d /tmp/codex-auths-import
# validate extracted files (same endpoint/rules), then copy passed ones:
cp <passed-json-files> /home/docker/CLIProxyAPI/auths/
```

## Hard delete (only after confirmation)

After user confirms, delete invalid directory and import temp folders:

```bash
rm -rf /home/docker/CLIProxyAPI/auths_invalid/*
rm -rf /tmp/codex-auths-import-<timestamp>
```

Bulk cleanup command (remove all historical import temp folders):

```bash
for d in /tmp/codex-auths-import-*; do
  [ -e "$d" ] && rm -rf "$d"
done
```

## Hourly scheduled validation & delete workflow

When user asks for hourly auto-clean:

1. Create a cron job (Asia/Shanghai) with `expr: 0 * * * *`.
2. Each run validates all `*.json` in `/home/docker/CLIProxyAPI/auths`.
3. Move all unqualified files into `auths_invalid_dir` (no hard delete) based on rules:
   - unqualified: 401/403, malformed JSON, missing token/account_id, `._*.json`
   - qualified: 200/429
4. Send user summary after each run in Chinese:
   - 总共：<总数> 个
   - 合格的：<合格数> 个
   - 不合格并删除的：<删除数> 个
   - 删除原因统计：<按原因计数>
5. If failed, report error and processed progress.

Recommended cron payload style（结论）：
- **不要用 OpenClaw cron 来做"每小时跑脚本+发TG"**（详见 `reports/lock-incident.md`）。
- 该场景必须用**系统 crontab + `scripts/hourly-run-and-notify.sh`**。
- OpenClaw cron 仅保留用于"AI 学习巡检/总结"等纯 agentTurn 任务。

## 对话总结（阶段成果，需持续更新）

以下为本技能从0到1的关键对话沉淀（用于新维护者快速理解）：

1. **基础能力落地**：先实现 codex JSON 批量验证、失效清理、ZIP 导入。
2. **双目录分层**：将"有效有额度/有效无额度"拆分为 `auths_dir` 与 `auths_no_quota_dir`。
3. **稳定性修复**：新增每小时巡检并发锁，避免重叠执行导致统计波动。
4. **无效文件策略升级**：无效文件不直接删，统一入 `auths_invalid_dir` 并附原因，询问用户是否删除。
5. **多 provider 识别**：对齐 Cli-Proxy-API-Management-Center 类型体系，先识别 provider 再选择验证方式。
6. **归档接管能力**：支持 ZIP/7z，自动只处理 JSON，忽略代码和其他非 JSON 文件；新增专用脚本 `import-archive.mjs`。
7. **新手零配置体验**：用户只给一个目录即可自动接管：
   - `--auth-dir <auths_dir>`（只给“有额度目录 auths”）
   - 自动推导 `<auths_dir>_no_quota` / `<auths_dir>_invalid` / `reports`（与 auths 同级）
   - 若未提供则按 CPA 线索自动探测 `auth-dir`。
8. **自动化运维闭环**：固定 3 个定时任务（小时清理/每日学习/每日同步）。
9. **文档与仓库同步纪律**：任何改动必须同步 SKILL + WORKFLOW + README，并立即中文 commit + push。
10. **三层JWT过期检测**：优先级 JWT id_token exp > expired 字段 > last_refresh+7天，无法判断默认未过期继续 API 校验。
11. **refresh_token 自动续期**：过期先尝试续期并写回文件救回可用账号；invalid_grant 才 INVALID_EXPIRED；网络错误 TRANSIENT_KEEP。
12. **account_id 去重**：扫描顺序先有额度后无额度，优先保留有额度账号，重复移入 invalid（INVALID_DUPLICATE）。
13. **reports 目录自动清理**：hourly-reconcile 启动时自动清理旧报告，默认保留最近 72 个，--max-report-files 可配置。
14. **invalid 目录积累警告**：超过 500 个时打印警告，提示 `rm -rf .../auths_invalid/*` 清理命令。
15. **validate-auths.mjs 功能对齐**：加入三层过期检测 + refresh_token 续期 + account_id 去重，与 hourly/import 行为一致。
16. **续期失败不直接 INVALID（关键修复）**：refresh_token 失效时不直接丢弃，继续用原 access_token 走 API 校验——因为 OpenAI access_token 实际存活时间可能长于 `expired` 字段标注值。只有 API 返回 401 才最终判定失效。
17. **cron agentId 必须用 main（运维经验）**：isolated agent（如 fast-pool）需要独立 auth-profiles.json；若未配置则所有 isolated cron job 会静默 auth 失败，无任何运行输出，用户无感知。创建/修复 cron 任务时统一使用 `agentId: "main"`，sessionKey 对应 `agent:main:telegram:direct:<chatId>`。
18. **定时任务通知必须用系统 crontab + shell + curl（架构决策）**：OpenClaw cron 的两种模式（isolated agentTurn / main systemEvent）均不适合"无人值守执行 shell 脚本+发 TG"场景。isolated 模式需独立 auth key；main systemEvent 只入队文字不保证执行。**正确做法**：系统 crontab 直接跑 shell 脚本，curl 调 Telegram Bot API 发通知，完全不依赖 LLM session。新增 `scripts/hourly-run-and-notify.sh` 封装此逻辑。
19. **每日学习巡检规则升级（2026-03-14）**：从泛泛 `git pull + grep` 升级为精准接口追踪，明确只追 6 个维度（认证API / Token续期 / Account字段 / provider枚举 / JSON schema / 状态码语义）；时间从 00:00 调整为凌晨 01:00（上海）；输出格式固定化，每项必须明确说有/无变化；只有真实变化才更新 skill 文件，禁止无中生有。

## Learning rationale and evolution notes (must maintain)

Keep this section updated when environment, upstream API behavior, or source project logic changes.

### Why these rules were chosen

- `200` means credential is usable now, so keep.
- `429` usually means rate/usage exhaustion, not token death, so keep.
- `401/403` indicates invalid/unauthorized token/account context, so remove as useless.
- malformed JSON / missing `access_token` / missing `account_id` cannot pass API auth, so remove.
- `._*.json` are AppleDouble artifacts, not real auth files, so remove.

### What was learned from project analysis (CLI Proxy API Management Center)

From `router-for-me/Cli-Proxy-API-Management-Center`, codex quota UI and logic map to:

- Auth-files page quota refresh action uses quota loader flow.
- Codex quota backend endpoint: `https://chatgpt.com/backend-api/wham/usage`.
- **Required Codex usage request header**: `Chatgpt-Account-Id` (resolved from auth file `id_token` payload field `chatgpt_account_id` / `chatgptAccountId`).
- Usage payload schema (frontend expectation):
  - `plan_type`
  - `rate_limit` / `code_review_rate_limit` with `primary_window`/`secondary_window` and `used_percent`, plus `limit_reached`.
  - `additional_rate_limits[]` (for extra metered features).
- Auth-files status toggle behavior reverted to a dedicated backend endpoint:
  - `PATCH /auth-files/status` with body `{ name, disabled }`.
  - Response contains `{ status, disabled }` and UI uses returned `disabled` to confirm final state.
- Auth-files UI also treats `status_message` or `statusMessage` as the status message source.
- 403/404 are rendered as credential/update hints in UI, but operational cleanup here treats 401/403 as invalid credentials and 429 as pass.

### Mutable inputs (must re-verify when changed)

Always re-check these before running bulk cleanup in a new environment:

1. Auth directory path (default now: `/home/docker/CLIProxyAPI/auths`).
2. Validation endpoint and required headers.
3. JSON field schema (`type`, `access_token`, `account_id`).
4. User policy on invalid archive vs direct delete.
5. User policy on timeout/network handling.

### Learning update protocol

When new evidence appears (new GitHub version, API change, user policy change):

1. Record what changed and why in this section.
2. Update decision rules and execution commands.
3. Re-test with a small sample before full batch.
4. Keep success snapshots (counts + key reasons) for regression comparison.
5. If schedule behavior changes, update cron workflow text too.

### Claude 套餐检测接口更新（2026-03-13 巡检）

**来源：** `router-for-me/Cli-Proxy-API-Management-Center` commit `ccf90f8`（2026-03-07）

Claude 配额检测新增了 Profile API 接口来判断套餐类型：

- **新增端点：** `GET https://api.anthropic.com/api/oauth/profile`
  - 与 `GET https://api.anthropic.com/api/oauth/usage` 并发请求（`Promise.allSettled`）
  - 请求头：`CLAUDE_REQUEST_HEADERS`（与 usage 接口相同的 Bearer token）
- **关键响应字段：** `organization.rate_limit_tier`（字符串）
- **套餐映射表（`CLAUDE_PLAN_TYPE_MAP`）：**
  - `default_claude_max_5x` → `plan_max5`（Max 5x 套餐）
  - `default_claude_max_20x` → `plan_max20`（Max 20x 套餐）
  - `default_claude_pro` → `plan_pro`（Pro 套餐）
  - `default_claude_ai` → `plan_free`（免费版）
  - 未知 tier 值 → `plan_unknown`（保底）
  - `rate_limit_tier` 为空 / profile 请求失败 → `planType` 为 `null`（不影响 usage 判断）
- **注意：** profile 请求失败不影响 usage 数据解析，两者独立返回。

**对 skill 规则影响：** Claude auth 文件的额度展示现在可以额外展示套餐类型，但**核心 VALID/INVALID 判断逻辑不变**（仍依赖 usage 接口的 quota 窗口数据）。

---

### Lock management enhancement (2026-03-09)

`hourly-reconcile.mjs` lock file format was upgraded from `pid\n` to `pid\ntimestamp\n`.

New behavior:
- `LOCK_MAX_AGE_MS` (default 900000 = 15 min): stale threshold
- `cleanStaleLock()`: auto-removes lock if owning PID is dead OR lock age ≥ 15 min
- Prevents permanent cron stall from zombie lock files
- `--lock-max-age-ms` CLI flag allows override

Key: this means a lock surviving more than 15 minutes will be auto-cleared on next startup - no more manual `rm -f /tmp/codex-auths-hourly.lock` needed.

### Success snapshots (historical)

- Snapshot A: local auth dir full validation
  - total: 5125
  - kept: 3124
  - removed: 2001 (2000 AppleDouble + 1 auth_403)
- Snapshot B: zip import #1
  - total in zip: 50
  - imported: 50
  - failed: 0
- Snapshot C: zip import #2
  - total in zip: 1000
  - imported: 999
  - failed: 1 (auth_403)
- Snapshot D: 7z import
  - total in 7z: 1145
  - imported to auths: 1145
  - imported to auths_no_quota: 0
  - moved to auths_invalid: 0
  - status: VALID_QUOTA x1145
- Snapshot E: ZIP import #1 (auths_all---03fa8448...zip)
  - total files: 6302
  - json files: 6301
  - imported to auths (quota): 34
  - imported to auths_no_quota: 99
  - moved to auths_invalid: 6168 (INVALID_AUTH 401)
  - post-action: manually deleted 6168 INVALID_AUTH files
- Snapshot F: ZIP import #2 (auths_all---412a6374...zip)
  - total files: 6302
  - json files: 6301
  - INVALID_EXPIRED: 6300
  - INVALID_MISSING_FIELDS: 1
  - imported to auths: 0
  - imported to auths_no_quota: 0
  - post-action: manually cleaned auths_invalid (6434 files)

Maintain snapshots so future changes can be compared quickly.

## Required sync policy (codex-auths-validator)

For any change related to `codex-auths-validator` (rules, user path handling, API endpoint, headers, cron behavior, workflow, docs, scripts):

1. Update skill files immediately (`SKILL.md` / `WORKFLOW.md` / `scripts/*` as needed).
2. Update GitHub-facing documentation immediately (`README.md`) to keep repo usage guide consistent.
3. Commit immediately with a Chinese commit message.
4. Push immediately to GitHub repository `LSH160981/skills-codex-auths-validator`.
5. Reply with commit id(s) after push.

Do not delay bundling changes for later when they affect behavior or operation.

## Daily 01:00 GitHub learning workflow (Asia/Shanghai)

> **使命：本 skill 所有逻辑来自上游项目，必须追踪上游接口变化并及时同步。**

当需要创建/更新学习巡检任务时：

- Schedule: `0 1 * * *` (`Asia/Shanghai`)（凌晨 01:00，避开整点高峰）
- `sessionTarget: isolated`, `payload.kind: agentTurn`, `agentId: "main"`

### 只追这 6 个维度（其余不管）

| 维度 | 具体关注点 |
|------|-----------|
| 认证/校验 API | endpoint、请求头字段、响应结构、状态码语义 |
| Token 生命周期 | refresh_token 流程、expired 字段、id_token 结构、续期接口参数 |
| Account 体系 | account_id 字段名变更、chatgpt_account_id、多账户字段 |
| 新 provider/type | AuthFileType 枚举新增/废弃 |
| JSON schema | auth 文件新增/废弃字段 |
| 状态码语义 | 429/401/403 有没有新含义 |

### 执行流程（每次必须全部执行）

**Step 1：拉取本地镜像更新**
```bash
cd /root/.openclaw/workspace/tmp-cli-proxy && git fetch && git log HEAD..origin/main --oneline 2>/dev/null | head -20 || git log --oneline -5
cd /root/.openclaw/workspace/tmp-cpa && git fetch && git log HEAD..origin/main --oneline 2>/dev/null | head -20 || git log --oneline -5
# 有新 commit 则 git pull --rebase
```

**Step 2：精准 grep**
```bash
# 接口相关
grep -rn 'wham/usage\|backend-api/wham\|Chatgpt-Account-Id\|chatgpt_account_id\|chatgptAccountId' \
  /root/.openclaw/workspace/tmp-cli-proxy /root/.openclaw/workspace/tmp-cpa \
  --include='*.ts' --include='*.js' --include='*.vue' -l 2>/dev/null

# Token 续期
grep -rn 'refresh_token\|grant_type.*refresh\|oauth/token\|auth0.openai' \
  /root/.openclaw/workspace/tmp-cli-proxy /root/.openclaw/workspace/tmp-cpa \
  --include='*.ts' --include='*.js' -l 2>/dev/null

# Provider/type 枚举
grep -rn 'AuthFileType\|type.*codex\|type.*claude\|type.*gemini\|type.*qwen\|type.*kimi\|type.*vertex\|type.*iflow\|type.*antigravity' \
  /root/.openclaw/workspace/tmp-cli-proxy /root/.openclaw/workspace/tmp-cpa \
  --include='*.ts' --include='*.js' 2>/dev/null | head -40

# 状态码处理
grep -rn '401\|403\|429\|limit_reached\|used_percent\|rate_limit' \
  /root/.openclaw/workspace/tmp-cli-proxy /root/.openclaw/workspace/tmp-cpa \
  --include='*.ts' --include='*.js' 2>/dev/null | grep -v 'node_modules\|\.git' | head -40
```

**Step 3：GitHub Commits 检查**

用 `web_fetch` 或 `browser` 访问：
- `https://api.github.com/repos/router-for-me/Cli-Proxy-API-Management-Center/commits?per_page=10`
- `https://github.com/router-for-me/Cli-Proxy-API-Management-Center/commits/main`

重点看近 7 天 commits，提取标题含 auth/token/codex/quota/provider/api 的变更。

**Step 4：对比当前 skill 规则**

读取本 SKILL.md 的 "Decision rules"、"Pre-flight expiry check"、"设计盲区" 三节，判断：
- 有无新接口参数 → 更新 Decision rules
- 有无新 provider type → 更新 `lib/provider.mjs`
- 有无 JSON 字段变化 → 更新 `lib/provider.mjs#schemaValid`
- 状态码语义有无调整 → 更新 Decision rules
- refresh_token 接口有无变化（endpoint/client_id/grant_type）→ 更新 `lib/codex.mjs#tryRefreshToken`

**Step 5：按需同步（只有真实变化才更新）**

```
1. 更新 SKILL.md（对应章节 + Learning rationale 段落）
2. 如影响脚本逻辑：更新 lib/provider.mjs 或 lib/codex.mjs
3. 更新根目录 README.md（版本演进部分）
4. git add ... && git commit -m '学习更新: <一句话说什么变了>'
5. git push skills-origin master
```

### 输出格式（必须严格按此）

```
巡检时间（上海）：
检查了：

接口相关发现：
- wham/usage 端点：{无变化 / 有变化：xxx}
- 请求头：{无变化 / 有变化：xxx}
- Token 续期接口：{无变化 / 有变化：xxx}
- Provider/type 枚举：{无变化 / 新增：xxx}
- JSON schema：{无变化 / 有变化：xxx}
- 状态码语义：{无变化 / 有变化：xxx}

skill 更新：{未更新 / 已更新，commit: xxxx}
需要人工确认：{否 / 是，原因：xxx}
```

## Mandatory auto-provision on new machine (3 cron jobs)

When this skill is installed/used on a new machine, ALWAYS ensure these three cron jobs exist automatically (create if missing, update if exists by name):

### Job A: Hourly auth validation cleanup（系统 crontab）

> 结论：该任务必须用系统 crontab（OpenClaw cron 不可靠，详见 `reports/lock-incident.md`）。

- Name（crontab）：`Codex auths hourly-run-and-notify`
- Schedule：`0 * * * *`（Asia/Shanghai 在系统层面按服务器时区；如需严格上海时区请把服务器 TZ 设为 Asia/Shanghai）
- Command：

```bash
0 * * * * bash /root/.openclaw/workspace/skills/codex-auths-validator/scripts/hourly-run-and-notify.sh >> /tmp/codex-auths-cron.log 2>&1
```

- Behavior（双目录流转 + 稳定通知）：
  - 调用 `scripts/hourly-reconcile.mjs` 扫描并迁移：
    - `/home/docker/CLIProxyAPI/auths`
    - `/home/docker/CLIProxyAPI/auths_no_quota`
    - `/home/docker/CLIProxyAPI/auths_invalid`
  - 输出通过 Telegram Bot API 发送给用户（超长自动改发文件）
  - 失败重试 3 次（TG sendMessage/sendDocument）
  - 本地日志：`/tmp/codex-auths-cron.log` + `/tmp/codex-auths/hourly-reconcile-*.log`

- 依赖：
  - TG token/chatId **禁止硬编码在仓库文件**；必须按用户单独配置：使用环境变量或本机 secrets 文件（默认 `/root/.openclaw/secrets/codex-auths-validator.env`），换用户必须单独询问并单独存放。
  - 不依赖 OpenClaw agent session / auth-profiles.json

> OpenClaw cron 里的同名 job（如果存在）应禁用，避免重复跑。

### Job B: Daily 01:00 GitHub learning check（凌晨接口巡检）

- Name: `Codex auths 每日01:00 GitHub学习巡检（上海）`
- Schedule: `0 1 * * *` (`Asia/Shanghai`)
- 追踪维度：认证API / Token续期接口 / Account字段 / provider枚举 / JSON schema / 状态码语义
- 详见上方 "Daily 01:00 GitHub learning workflow" 章节

### Job C: Daily 00:00 skill self-sync (Asia/Shanghai)

- Name: `Codex auths 每日00:00 技能同步（上海）`
- Schedule: `0 0 * * *` (`Asia/Shanghai`)
- Behavior:
  - pull latest updates from `https://github.com/LSH160981/skills-codex-auths-validator.git`
  - sync `skills/codex-auths-validator/*` to local workspace
  - if changed, apply immediately and send update summary to user

### Idempotent enforcement rule

Every time this skill runs in a new environment:
1. `cron.list(includeDisabled=true)`
2. find jobs by exact name
3. create missing jobs via `cron.add`
4. patch existing jobs via `cron.update` to keep schedule/payload consistent
5. report ensured job IDs to user

This guarantees all required cron jobs auto-appear after skill deployment on any machine.

## Multi-user path policy (important)

This skill must work for any user environment, not only `/home/docker/CLIProxyAPI/auths`.

### 通用一条指令能力（新增）

只要用户告诉一个 JSON 存放目录，skill 就必须自动接管并完成工作：

1. 把该目录直接作为 `auths_dir`。
2. 自动派生并创建：
   - `auths_no_quota_dir = <auths_dir>_no_quota`
   - `auths_invalid_dir = <auths_dir>_invalid`
3. 自动执行校验、分层、无效归档、结果汇总。
4. 自动创建/修复定时任务（无需用户额外配置细节）。

When user provides a JSON folder path, the skill should:
1. Accept the path directly as `auths_dir`.
2. Derive `auths_no_quota_dir` as `<auths_dir>_no_quota` unless user specifies another path.
3. Derive `auths_invalid_dir` as `<auths_dir>_invalid` unless user specifies another path.
4. Create missing target directories automatically.
5. Run the same validation/migration/archive rules without asking extra setup questions.

If no path is provided, run discovery first; only ask user when discovery has low confidence.

## 设计盲区与边界条件（你可能没想到的地方）

> 以下问题均已在代码中修复或给出处理策略，记录在此供后续维护参考。

### 1. 跨文件系统移动（EXDEV）
```
问题：renameSync 在 /tmp → /home/docker 等不同挂载点之间会抛 EXDEV
影响：所有 safeMove 调用（hourly-reconcile / validate-auths / import-archive）均受影响
伪代码：
  try { fs.renameSync(src, dst) }
  catch (err) {
    if (err.code === 'EXDEV') {
      fs.copyFileSync(src, dst)  // 先复制
      fs.unlinkSync(src)          // 再删源
    } else throw err
  }
修复：已在 lib/codex.mjs#safeMove 统一实现，三脚本共用
```

### 2. listJson 把目录/符号链接当文件
```
问题：readdirSync().filter(.endsWith('.json')) 不检查是否为普通文件
风险：名为 foo.json 的目录或 symlink 进入流程 → JSON.parse 失败 → 误入 invalid
     恶意 symlink 指向 /etc/passwd → readFileSync 读取敏感文件
伪代码：
  files = readdirSync(dir)
    .filter(f => f.endsWith('.json'))
    .filter(f => lstatSync(join(dir, f)).isFile())  // ← 加这一行
修复：已在 lib/codex.mjs#listJsonFiles 统一实现

补充修复：listJsonFiles 对目录不可读/不存在加 try/catch，直接返回 []，避免 cron 整体崩溃
```

### 3. 非 codex 文件在 hourly-reconcile 被误判为 invalid
```
问题：旧代码 if type !== 'codex' → to_invalid('non_codex')
影响：用户 auths 目录里若混有 claude/gemini JSON，每次巡检都被移入 invalid
修复：改为 detectProvider() + validateSchemaWithReason()
  schema 有效 → keep（SCHEMA_VALID_PROVIDER，不动位置）
  schema 无效 → to_invalid（才算真的无效）
现在与 validate-auths.mjs 的逻辑一致
```

### 4. dedupRemoved 没写入 report JSON（统计盲区）
```
问题：dedup 删除数只 console.log 出来，report JSON 里没有此字段
影响：hourly-run-and-notify.sh 解析 report 时 DEDUP 永远是 0，TG 摘要数字不准
修复：report JSON 新增 dedupRemoved 字段；shell 脚本解析时同步读取
```

### 5. validate-auths.mjs 的 import 语句位于函数定义之后
```
问题：ESM 静态 import 应在文件顶部；混在 const/function 之间虽然语义上 Node.js
     能处理（import 被提升），但工具链（linter/bundler）可能报错或行为异常
修复：重写 validate-auths.mjs，所有 import 统一移到文件顶部
```

### 6. Telegram 4096 字符上限
```
问题：sendMessage 单条最多 4096 字节，超长静默截断（或 API 400 错误）
场景：无效原因统计很多时（如 6000 个文件结果），摘要超长
伪代码：
  if len(text) > 4000:
    write text to tmp file
    sendDocument(tmp_file, caption="消息超长，以文件发送")
  else:
    sendMessage(text)
修复：已在 hourly-run-and-notify.sh#tg_send_text 实现自动分片
```

### 7. refreshedCount / dedupRemoved 在 TG 摘要中缺失
```
问题：shell 脚本只解析了 checkedTotal/finalQuota 等，没有读取 refreshedCount / dedupRemoved
影响：用户看不到"本次续期了几个 token"，无法感知续期效果
修复：hourly-run-and-notify.sh 中新增 REFRESHED / DEDUP 字段解析，摘要中展示
```

### 8. 原子写（writeJsonAtomic）防止写半段崩溃
```
问题：续期成功后 writeFileSync 直接写入原文件；若进程中途被 kill，文件变为空或半写
影响：下次读取该文件 → JSON.parse 失败 → 误判 INVALID_JSON → 有效账号丢失
伪代码：
  tmp = file + '.tmp-' + pid + '-' + Date.now()
  writeFileSync(tmp, data)   // 先写临时文件
  renameSync(tmp, file)       // 原子替换（同设备内 rename 是原子的）
修复：已在 lib/codex.mjs#writeJsonAtomic 实现，三脚本共用
```

### 9. 同一 token 被两个并发 worker 同时 refresh
```
问题：同一文件若同时出现在 DIR_QUOTA 和 DIR_NO_QUOTA（去重之前的窗口），
     两个 worker 同时读取 → 同时 tryRefreshToken → 第一个写回文件
     第二个写回时覆盖第一个的结果（可能是更旧的 token）
伪代码（最简防护）：
  processing = new Set()
  worker():
    if fullPath in processing: ops.push({action:'keep', reason:'concurrent_skip'})
    else:
      processing.add(fullPath)
      ... 正常处理 ...
      processing.delete(fullPath)
当前状态：去重发生在 dedup 阶段（校验之前），理论上不会有同一文件路径被两个 worker 抢。
但 DIR_QUOTA 和 DIR_NO_QUOTA 里可能有相同 account_id 的不同文件，各自被处理 → 两个 refresh
请求 → 两次写回不同文件（实际上互不干扰，因为是两个不同的 fullPath）。
结论：当前实现安全，不需要加锁。
```

### 10. import-archive.mjs 子目录同名文件 basename 冲突
```
问题：archive 里 a/auth.json 和 b/auth.json 的 basename 都是 auth.json
     旧实现用 jsonFiles.find(f => path.basename(f) === name) 回查 src
     → 会找错文件，造成“验证 A 文件但复制了 B 文件”的数据正确性风险
修复：results 里直接记录 fullPath，不再用 basename 反查；导入时直接用 fullPath
建议（可选增强）：同时记录 originalRelPath（archive 内相对路径），report 里展示映射更易溯源
```

### 11. reports 目录排序不稳定（mtime 精度问题）
```
问题：同一秒内生成的多个 report 文件，sort by mtime 可能顺序不定
影响：pruneReportDir 删除“最老”文件时可能误删同批
修复：按文件名字典序排序（文件名含时间戳，字典序=时间序），不依赖 mtime
```

### 12. writeJsonAtomic 遗留 .tmp 垃圾文件
```
问题：writeJsonAtomic 在 kill -9 时可能遗留 .<base>.tmp-PID-TS 文件
影响：目录污染（不影响校验，但长期堆积）
修复：
  1) lib/codex.mjs 新增 cleanTmpFiles(dir)
  2) hourly-reconcile.mjs 启动时清理 DIR_QUOTA / DIR_NO_QUOTA
  3) hourly-run-and-notify.sh 启动时 find -delete 清理
```

## 事故 / Bug / 事故复盘（统一归档）

历史事故与 bug 记录统一归档到：`skills/codex-auths-validator/reports/lock-incident.md`（仓库唯一真相）。

- 请不要在 SKILL.md 中继续追加长篇事故日志
- SKILL.md 只保留结论与指向该报告的链接

