先记住主线:
uv run python main.py init
uv run python main.py doctor --fix
uv run python main.py --mode lite
# 或
uv run python main.py --mode full
如果排障前还没跑 doctor --fix, 先跑它.
uv run python main.py status
uv run python main.py doctor --fix
启动后再看 API:
export ZHUCE6_BASE_URL="http://<dashboard-host>:<dashboard-port>"
curl -sS "$ZHUCE6_BASE_URL/api/runtime" | python3 -m json.tool | head -n 80
curl -sS "$ZHUCE6_BASE_URL/api/health/dependencies" | python3 -m json.tool | head -n 80
init 之后仍然跑不起来优先检查:
.env 是否是本次向导写入的目标文件doctor --fix 是否已执行--mode lite 或 --mode fullbackend 是否与你填写的后端配置一致现在新配置支持两条 cfmail 初始化路径:
Cloudflare API Token + zone_nameCF_AUTH_EMAIL + CF_AUTH_KEY + zone_name + worker_domain如果向导已经跑过, 先检查这些结果是否存在:
config/cfmail_accounts.jsonconfig/cfmail_provision.env.env 中的:
ZHUCE6_CFMAIL_API_TOKENZHUCE6_CFMAIL_CF_AUTH_EMAILZHUCE6_CFMAIL_CF_AUTH_KEYZHUCE6_CFMAIL_CF_ACCOUNT_IDZHUCE6_CFMAIL_CF_ZONE_IDZHUCE6_CFMAIL_WORKER_NAMEZHUCE6_CFMAIL_ZONE_NAME如果仓库内已有 cfmail 配置, init 默认会提示复用. 若你误选了重新生成, 应先对照现有 config/ 内容确认是否与 live 配置一致.
uv run python scripts/setup_cfmail.py --help
注意:
scripts/setup_cfmail.py 的完整部署链依赖 wrangler.wrangler 在非交互环境下要求 Cloudflare API Token.CF_AUTH_EMAIL + CF_AUTH_KEY, 不要指望它完成首次 Worker 部署, 应该在 init 时直接填写已部署的 worker_domain.uv run python scripts/setup_cfmail.py --api-token <token> --zone-name <zone> 这条路径.email_domain 已经失效, init 在保存阶段会尝试自动轮换到新的可用子域名. 若仍失败, 再手工检查 DNS 与 Email Routing.doctor --fix 之后还有依赖问题doctor --fix 当前会自动处理:
uv syncnpm install --no-fund --no-audit它不会替你全局安装:
gitnodenpmnpxsslocal所以如果报告里仍有失败项, 直接按报告补齐系统依赖即可.
如果 d1_cleanup 一直跳过, 再检查:
ZHUCE6_D1_DATABASE_ID 是否为空init / cfmail 向导自动写入优先检查:
ZHUCE6_REGISTER_PROXYZHUCE6_PROXY_POOL_DIRECT_URLS如果你选择 Clash YAML 模式, 还要确认:
command -v sslocal || command -v ss-local || true
如果没有 sslocal, 先安装它, 再重新执行:
uv run python main.py doctor --fix
full + cpa 不可用重点检查:
ZHUCE6_BACKEND=cpaZHUCE6_CPA_MANAGEMENT_BASE_URLZHUCE6_CPA_MANAGEMENT_KEY然后看:
curl -sS "$ZHUCE6_BASE_URL/api/health/dependencies" | python3 -m json.tool | head -n 80
full + sub2api 不可用重点检查:
ZHUCE6_BACKEND=sub2apiZHUCE6_SUB2API_BASE_URLZHUCE6_SUB2API_API_KEYZHUCE6_SUB2API_ADMIN_EMAIL + ZHUCE6_SUB2API_ADMIN_PASSWORDdoctor 与 /api/health/dependencies 都会明确显示 sub2api ready / unavailable.
先看 /api/runtime:
runtime_moderegistered_tasksregister_enabled常见原因:
dashboardZHUCE6_REGISTER_ENABLED=falsecfmail 运行时变量不完整python, uv, node, npm, npx, git 可用.sslocal.如果你需要从别的前端域读取 /api/runtime 或 /api/summary, 还要配置:
ZHUCE6_DASHBOARD_ALLOWED_ORIGINS=http://your-dashboard.example.com注册日志所有线程卡在 wait_otp, 180s 超时:
[zhuce6:register] [thread-1] ❌ failed [stage=wait_otp]: otp retrieval failed
↳ verification code timed out after 181.88s
↳ otp mailbox diagnostics: polls=45 scanned=0
cfmail 子域名的 DNS 记录 (MX + SPF) 缺失. 没有 MX 记录, Cloudflare 无法接收邮件, OTP 永远到不了.
source config/cfmail_provision.env
# 检查子域名 DNS 记录数量 (应 >= 4: 3xMX + 1xTXT)
curl -s "https://api.cloudflare.com/client/v4/zones/$ZHUCE6_CFMAIL_CF_ZONE_ID/dns_records?name=<subdomain>.example.com" \
-H "X-Auth-Email: $ZHUCE6_CFMAIL_CF_AUTH_EMAIL" \
-H "X-Auth-Key: $ZHUCE6_CFMAIL_CF_AUTH_KEY" \
| python3 -c "import sys,json; r=json.load(sys.stdin); print(f'records: {r[\"result_info\"][\"total_count\"]}')"
source config/cfmail_provision.env
uv run python -c "
from core.cfmail_provisioner import CfmailProvisioner
p = CfmailProvisioner()
for acct in p._load_all_accounts():
if not acct.get('enabled'): continue
domain = acct['email_domain']
label = acct['name'].replace('cfmail-', '')
try:
p._create_email_routing_rule(domain, label)
p._create_dns_records(domain)
p._update_worker_domains(domain)
print(f'fixed {domain}')
except Exception as e:
print(f'error {domain}: {e}')
"
[zhuce6:register] [thread-13] ❌ exception: no proxy available in pool
ZHUCE6_REGISTER_THREADS 大于 ZHUCE6_PROXY_POOL_SIZE. 多余线程拿不到代理.
确保线程数 <= 代理池大小:
# .env
ZHUCE6_REGISTER_THREADS=12
ZHUCE6_PROXY_POOL_SIZE=12
401 no_organization先看 /api/account-survival 或 state/responses_survival_tracker.json:
first_invalid_error_coderegistration_proxy_regionregistration_post_create_gatefirst_use_proxy_keyfirst_use_proxy_regionfingerprint_consistent当前优先排查顺序:
usregistration_post_create_gate = add_phone如果 fresh 号主要出现在 us, 先把:
ZHUCE6_REGISTER_FRESH_PROXY_REGIONS=tw,jp,hk,us
如果 live 日志里连续出现 registration_disallowed, 说明当前活动域已经被上游集中拒绝. 这时不要继续等满整个黑名单窗口, 应确认:
ZHUCE6_CFMAIL_REGISTRATION_DISALLOWED_THRESHOLD=2
这样同域连续 2 次 registration_disallowed 就会直接退域补位.
并重启 runtime.
如果 cohort 里大多数账号没有:
registration_fingerprint_profileregistration_proxy_region说明 reseed 仍在吃旧历史号, 这会污染实验.
优先检查:
grep -n "ZHUCE6_RESPONSES_SURVIVAL" .env
建议:
ZHUCE6_RESPONSES_SURVIVAL_REQUIRE_PROVENANCE=true
ZHUCE6_RESPONSES_SURVIVAL_RECENT_WINDOW_SECONDS=1800
这类号现在不会走旧候选池, 但会在单池里带 warmup 风险字段:
warmup_required=truewarmup_state=pending|passed|failed判断方法:
pending: 还在观察窗口内passed: 已满足最小成功探测数或最小存活时间failed: 首次 invalid 发生在 warmup 期间如果这类号比例过高, 不要先怀疑 dashboard, 先看:
first_invalid_error_codefirst_invalid_proxy_region每次启动注册机生成 12 个 sslocal, 但停止时不清理. 长期积累后系统有上百个 sslocal 进程.
重启前先清理:
pkill -f sslocal
create_account 已 200, 但 5 分钟 success 看起来是 0 / 全是 add_phone_gatelogs/register.log 里能看到 create_account status: 200线程最终却频繁报:
[zhuce6:register] [thread-1] ❌ failed [stage=add_phone_gate]: post-create flow requires phone gate
同时日志里还能看到:
📥 deferred token retry enqueued
这通常不是 create_account 本身失败, 而是 add_phone 后的 token 恢复链被拖慢或被误判:
0%.当前实现已经做了两层处理:
create_account 已直接返回 https://chatgpt.com/api/auth/callback/openai?..., 先走 callback/openai -> /api/auth/session 直取 token.create_account 命中 add_phone 后, 再尝试 direct session token 提取.60s.优先同时看两类日志:
grep -n "add_phone_gate\\|deferred token retry enqueued\\|deferred token acquired\\|direct session token" logs/register.log | tail -n 80
如果你能看到:
post-create add_phone: attempting direct session token extraction📥 deferred token retry enqueued[pending] ✅ deferred token acquired说明问题在 token 恢复时序, 不是 create_account 没成功.
add_phone_gate, 没有任何 [pending] 成功: 再检查代码是否已包含 direct session token 路径与 60s pending retry 限制.[pending] ✅ deferred token acquired: 说明账号并非 0 成功, 只是不能只按线程即时结果统计.create_account 后直接丢掉了 callback/session 结果, 又重新触发 fresh login, 这会显著提高 add_phone 命中率.account unavailable / mailbox 阶段持续失败注册日志持续出现:
[zhuce6:register] [thread-1] ❌ failed [stage=mailbox]: cfmail account unavailable, current accounts: 无
或者线程长时间停在 mailbox 失败, 没有新的 cfmail 子域名被切出.
这是 cfmail 全域 cooldown 场景:
CfmailAccountManager cooldown.select_account() 返回 None, register 在 mailbox 阶段直接失败.unsupported_email / registration_disallowed 信号时, 不会靠黑名单窗口自然触发 rotation.当前实现已经在 register worker 顶部检测这个状态, 一旦发现所有 cfmail account 都不可选, 会主动调用 CfmailProvisioner.rotate_active_domain() 打破死锁.
看 logs/register.log 是否出现:
[cfmail] all accounts in cooldown, forcing domain rotation to break deadlock
如果有, 说明死锁检测已经触发, 接着只需要看 rotation 是否成功.
set -a && source .env && set +a
PYTHONPATH=. uv run python -c "
from core.cfmail_provisioner import CfmailProvisioner
p = CfmailProvisioner()
result = p.rotate_active_domain()
print(f'success={result.success}, new={result.new_domain}, error={result.error}')
"
如果这里成功, 但 register 仍不恢复, 再检查:
config/cfmail_accounts.json 中是否已有新的 enabled 子域名rotate_active_domain() 因 DNS read-only / code 1043 报错手工调用或自动 rotation 时, 日志出现类似:
HTTP 400 {"errors":[{"code":1043,"message":"DNS record is read only"}]}
Cloudflare 某些历史 DNS record 或 email routing rule 可能是只读或受保护资源. 这些资源常出现在旧 auto 域名被切换后的残留清理阶段.
当前实现里:
_delete_domain_artifacts() 对删除失败按 best-effort 处理cleanup_stale_domains() 会跳过 read-only DNS / routing rulerotate_active_domain() 在新域名已经完成 DNS + routing + worker binding + smoke test 后, 即使 cleanup 失败也不会回滚整个 rotation所以 1043 更应被视为旧资源清理告警, 而不是新域名切换失败.
如果怀疑 rotation 没真正切过去, 重点看最终结果而不是 cleanup 告警:
set -a && source .env && set +a
PYTHONPATH=. uv run python -c "
from core.cfmail_provisioner import CfmailProvisioner
p = CfmailProvisioner()
result = p.rotate_active_domain()
print(result)
"
只要输出里 success=True, 并且 new_domain 已变更, 就说明 rotation 主链成功.
auth-files 数量明显小于本地 pool/*.jsoncpa_count 突然下降当前稳定结构不是“本地 pool 单池”, 而是:
pool/*.json: 持久化备份因此 CPA 丢库存时, 正确修复动作不是重新解释成双池晋升, 而是做 backup reconcile.
rotate 周期任务也会检测 driftpool/*.json 回灌到 CPAuv run python main.py status
curl -sS "$ZHUCE6_BASE_URL/api/summary" | python3 -m json.tool | head -n 80
重点看:
pool_countcpa_counttotal_attempts / total_successpool/cpa_runtime_reconcile_state.jsonpool_count 明显大于 cpa_count, 优先看 reconcile 是否正在补回.total_attempts 持续增长但 total_success 不动, 且失败热点出现 cpa_sync_failed, 说明 register 新号写 backend 失败, 应先排查 CPA Management API 或网络错误.当前实现中, validate, rotate, cleanup 都应双删 backend + pool.
如果你仍看到“backend 已删但 pool 还在”的残留, 先确认代码是否为最新版本, 再复查对应任务日志:
validate: deletedrotate: deleted_401cleanup: deletedcreate_account 已 200, 但后续一直停在:
https://auth.openai.com/add-phone
日志里反复出现:
workspace list missing in auth session payload
solution D: no accessToken in session response, keys=['WARNING_BANNER']
fresh login fallback 做完后, 仍然没有:
这类 hard add_phone 已被 live 证实为服务端会话状态问题, 不是本地漏流程:
oai-client-auth-session 里没有 workspaces.https://auth.openai.com/api/accounts/client_auth_session_dump 已经可打通, 但返回仍然是:
status = 200workspace_count = 0/api/auth/session 仍然只返回:
WARNING_BANNERaccessToken这说明:
workspaces / orgs.优先看 live register 日志:
grep -n "client auth session dump\\|workspace list missing\\|WARNING_BANNER\\|add-phone trace saved" /home/sophomores/zhuce6/logs/main_full_4threads_*.log | tail -n 80
若能同时看到:
client auth session dump status: 200client auth session dump workspace count: 0workspace list missing in auth session payloadsolution D: no accessToken in session response, keys=['WARNING_BANNER']就说明当前不是本地漏掉 workspace route, 而是服务端会话本身没有给 workspace / org.
client_auth_session_dump 已经是 200, 但 workspace_count 仍为 0:
auth_session_workspace_count = 0auth_session_dump_workspace_count = 0direct_session_keys = ["WARNING_BANNER"]/add-phone
说明这就是 server-side gate.