docs: add token-lite automation flow

This commit is contained in:
2026-05-10 13:05:54 +08:00
parent dc7e952c43
commit ba6d673650
4 changed files with 457 additions and 2 deletions
+207
View File
@@ -0,0 +1,207 @@
#!/usr/bin/env python3
"""
Token-lite Codex OAuth onboarding runner skeleton.
Purpose: keep future Codex2API/OpenAI/5sim onboarding runs script-driven with compact JSONL events.
This script intentionally centralizes config parsing, redaction, state classification, and event logging.
Sensitive values are never printed directly.
"""
from __future__ import annotations
import argparse
import datetime as dt
import json
import os
import pathlib
import re
import shlex
import subprocess
import sys
import time
import urllib.parse
import urllib.request
from typing import Any, Dict, Iterable, List, Optional, Tuple
COUNTRY_META = {
"argentina": {"label_zh": "阿根廷", "prefix": "54"},
"netherlands": {"label_zh": "荷兰", "prefix": "31"},
"indonesia": {"label_zh": "印度尼西亚", "prefix": "62"},
"vietnam": {"label_zh": "越南", "prefix": "84"},
"england": {"label_zh": "英国", "prefix": "44"},
"japan": {"label_zh": "日本", "prefix": "81"},
"germany": {"label_zh": "德国", "prefix": "49"},
}
SECRET_KEY_RE = re.compile(r"(KEY|TOKEN|SECRET|PASSWORD|PASS|CODE|CALLBACK)", re.I)
DIRECT_PHONE_REJECT = "无法向此电话号码发送验证码"
INVALID_PHONE = "电话号码无效"
ACCOUNT_DEACTIVATED = "account_deactivated"
def now() -> str:
return dt.datetime.now().isoformat(timespec="seconds")
def load_env(path: pathlib.Path) -> Dict[str, str]:
env: Dict[str, str] = {}
for raw in path.read_text().splitlines():
line = raw.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
env[k.strip()] = v.strip().strip('"\'')
return env
def redact_key_value(key: str, value: Any) -> Any:
if value is None:
return None
if SECRET_KEY_RE.search(key):
return "[REDACTED]" if str(value) else ""
if isinstance(value, str) and "code=" in value and "state=" in value:
return re.sub(r"code=[^&]+", "code=[REDACTED]", value)
return value
def redact_event(event: Dict[str, Any]) -> Dict[str, Any]:
return {k: redact_key_value(k, v) for k, v in event.items()}
class RunLog:
def __init__(self, run_dir: pathlib.Path):
self.run_dir = run_dir
self.run_dir.mkdir(parents=True, exist_ok=True)
(self.run_dir / "screenshots").mkdir(exist_ok=True)
self.events_path = self.run_dir / "events.jsonl"
def emit(self, phase: str, result: str = "info", **fields: Any) -> None:
event = {"ts": now(), "phase": phase, "result": result, **fields}
safe = redact_event(event)
with self.events_path.open("a") as f:
f.write(json.dumps(safe, ensure_ascii=False, separators=(",", ":")) + "\n")
print(json.dumps(safe, ensure_ascii=False, separators=(",", ":")), flush=True)
def run(cmd: str, timeout: int = 30, input_text: Optional[str] = None) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["bash", "-lc", cmd],
input=input_text,
text=True,
capture_output=True,
timeout=timeout,
)
def cdp_eval(js: str, match: str = "auth.openai.com", timeout: int = 45) -> str:
proc = run(f"CDP_MATCH={shlex.quote(match)} python3 /tmp/cdp_async_eval.py", timeout=timeout, input_text=js)
return (proc.stdout or "") + (proc.stderr or "")
def classify_page(raw: str) -> str:
if ACCOUNT_DEACTIVATED in raw:
return "account_deactivated"
if "Cloudflare" in raw or "验证您是真人" in raw or "challenge" in raw.lower():
return "cloudflare_or_challenge"
if "auth.openai.com/log-in" in raw or "欢迎回来" in raw:
return "login_page"
if "email-verification" in raw or "检查你的收件箱" in raw:
return "email_code_page"
if "add-phone" in raw or "电话号码是必填项" in raw:
return "add_phone_page"
if "重新发送" in raw and ("验证码" in raw or "代码" in raw):
return "phone_code_page"
if DIRECT_PHONE_REJECT in raw:
return "phone_direct_reject"
return "unknown"
def country_order(env: Dict[str, str]) -> List[str]:
order = [x.strip().lower() for x in env.get("FIVE_SIM_COUNTRY_ORDER", "").split(",") if x.strip()]
if order:
return order
country_id = env.get("FIVE_SIM_COUNTRY_ID", "").strip().lower()
if country_id:
return [country_id]
return ["vietnam"]
def codex2api_origin(env: Dict[str, str]) -> str:
raw = env.get("CODEX2API_URL", "").strip().rstrip("/")
u = urllib.parse.urlsplit(raw)
return f"{u.scheme}://{u.netloc}" if u.scheme and u.netloc else raw
def generate_codex2api_oauth_url(env: Dict[str, str]) -> Dict[str, Any]:
origin = codex2api_origin(env)
req = urllib.request.Request(
origin + "/api/admin/oauth/generate-auth-url",
data=b"{}",
headers={"X-Admin-Key": env.get("CODEX2API_ADMIN_KEY", ""), "Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=20) as resp:
return json.loads(resp.read().decode())
def open_cdp_url(url: str) -> None:
req = urllib.request.Request("http://127.0.0.1:9223/json/new?" + urllib.parse.quote(url, safe=""), method="PUT")
with urllib.request.urlopen(req, timeout=10) as resp:
resp.read()
def probe_proxy(proxy: str) -> Tuple[bool, str]:
if proxy.startswith("socks5://"):
curl_proxy = "socks5h://" + proxy[len("socks5://") :]
else:
curl_proxy = proxy
proc = run(f"curl --proxy {shlex.quote(curl_proxy)} --max-time 8 https://api.ipify.org?format=json", timeout=12)
ok = proc.returncode == 0 and "ip" in proc.stdout
return ok, proc.stdout.strip()[-120:]
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--env", default="~/.hermes/env/codex_oauth_onboarding.env")
ap.add_argument("--run-root", default="~/.Hermes/workspace/runs/codex-oauth")
ap.add_argument("--print-plan", action="store_true", help="Only print resolved low-token plan/config summary.")
args = ap.parse_args()
env_path = pathlib.Path(args.env).expanduser()
env = load_env(env_path)
run_id = dt.datetime.now().strftime("%Y%m%d-%H%M%S")
log = RunLog(pathlib.Path(args.run_root).expanduser() / run_id)
countries = country_order(env)
proxy = env.get("BROWSER_PROXY_SERVER", "")
replacement_limit = int(env.get("PHONE_VERIFICATION_REPLACEMENT_LIMIT") or "3")
log.emit(
"preflight",
"config_loaded",
env_path=str(env_path),
target_mode=env.get("PANEL_MODE") or env.get("OAUTH_TARGET_MODE") or "codex2api",
proxy=proxy,
country_order=",".join(countries),
replacement_limit=replacement_limit,
five_sim_product=env.get("FIVE_SIM_PRODUCT") or env.get("FIVE_SIM_SERVICE") or "openai",
operator=env.get("FIVE_SIM_OPERATOR") or "any",
)
if proxy:
ok, info = probe_proxy(proxy)
log.emit("preflight", "proxy_ok" if ok else "proxy_failed", proxy=proxy, info=info)
if not ok:
return 2
if args.print_plan:
log.emit("plan", "ready", next="run full automation script implementation/resume")
return 0
log.emit(
"not_implemented",
"skeleton_only",
message="This script currently provides the token-lite logging/config skeleton. Add browser/email/phone step execution before using as the primary runner.",
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+4 -2
View File
@@ -47,7 +47,7 @@ These are confirmed operating rules for BOSS's environment:
- The new account password is unified through `CUSTOM_PASSWORD`. If it is empty, stop and ask BOSS to fill it, unless BOSS explicitly allows auto-generation for that run.
- ClawEmail creates a fresh mailbox for every run (`CLAWEMAIL_CREATE_PER_RUN=true`). Record every created mailbox locally with outcome classification: pending, success, failed.
- Phone/SMS platform has no default. Keep `PHONE_VERIFICATION_ENABLED=false` and `PHONE_SMS_PROVIDER` empty unless BOSS configures one or approves enabling it after a phone verification appears.
- If phone verification appears and BOSS has configured 5sim, use **SMS activation for `openai`**, not WhatsApp receive channels. The original extension's 5sim provider buys `/v1/user/buy/activation/{country}/{operator}/openai`, polls `/v1/user/check/{activationId}`, and finishes/cancels the activation. Follow the configured `FIVE_SIM_COUNTRY_ID` first; if it is empty, the extension default is `vietnam`. Do **not** silently switch countries after a rejected number unless BOSS changes config or approves fallback. Before buying/submitting a non-USA number, verify the OpenAI visible country selector is already the expected country (for example `越南 (+84)`); if it reverted to `美国 (+1)`, fix the selector first or the number will be parsed as invalid. Use visible UI paste/click on `add-phone` when possible because CDP-only input mutation can desync React state and revert the country on submit. If a number is **not immediately rejected**, do not cancel it after one polling timeout: poll 5sim, click OpenAI `重新发送`, poll again, and retry resend at least once before canceling/rotating. If changing proxy (for example `1085``1083`), expect OpenAI auth session invalidation and restart from a fresh target OAuth URL. Codex2API env URLs may point at `/admin/accounts`; derive the API origin before calling `/api/admin/oauth/generate-auth-url`. See `references/openai-add-phone-5sim-sms-activation-2026-05-10.md`, `references/openai-phone-verification-5sim-sms-2026-05-10.md`, and `references/openai-phone-verification-proxy1083-resend-2026-05-10.md` for observed behavior, resend strategy, proxy-switch recovery, and pitfalls.
- If phone verification appears and BOSS has configured 5sim, use **SMS activation for `openai`**, not WhatsApp receive channels. The original extension's 5sim provider buys `/v1/user/buy/activation/{country}/{operator}/openai`, polls `/v1/user/check/{activationId}`, and finishes/cancels the activation. **Country selection priority is `FIVE_SIM_COUNTRY_ORDER` first** (comma-separated list such as `argentina,netherlands,indonesia`), then `FIVE_SIM_COUNTRY_ID`, then the original extension default `vietnam` only if both are empty. For each configured country, rotate numbers up to `PHONE_VERIFICATION_REPLACEMENT_LIMIT`; do **not** silently switch outside the configured order unless BOSS approves. Before buying/submitting a non-USA number, verify the OpenAI visible country selector is already the expected country (for example `阿根廷 (+54)`, `荷兰 (+31)`, `印度尼西亚 (+62)`, or `越南 (+84)`); if it reverted to `美国 (+1)`, fix the selector first or the number will be parsed as invalid. Use visible UI paste/click on `add-phone` when possible because CDP-only input mutation can desync React state and revert the country on submit. If a number is **not immediately rejected**, do not cancel it after one polling timeout: poll 5sim, click OpenAI `重新发送`, poll again, and retry resend at least once before canceling/rotating. If changing proxy (for example `1085``1083` or back), expect OpenAI auth session invalidation and restart from a fresh target OAuth URL. Codex2API env URLs may point at `/admin/accounts`; derive the API origin before calling `/api/admin/oauth/generate-auth-url`. If OpenAI returns `account_deactivated` after email verification, stop the current account and start with a new ClawEmail sub-mailbox instead of burning 5sim numbers. See `references/openai-add-phone-5sim-sms-activation-2026-05-10.md`, `references/openai-phone-verification-5sim-sms-2026-05-10.md`, `references/openai-phone-verification-proxy1083-resend-2026-05-10.md`, and `references/codex-oauth-token-lite-automation-flow-2026-05-10.md` for observed behavior, resend strategy, proxy-switch recovery, and pitfalls.
- Plus mode uses `PLUS_PAYMENT_METHOD=gopay`. Any paid GoPay action still requires explicit confirmation immediately before payment/approval.
- GoPay WhatsApp OTP uses **方案 A / manual checkpoint**: set `GOPAY_OTP_SOURCE=manual`. The extension detects OTP input and opens a side-panel prompt (`requestGoPayOtpInput` / “输入 GoPay 验证码”); BOSS pastes the WhatsApp code, then the extension fills OTP, fills `GOPAY_PIN`, and continues.
- `GOPAY_OTP` should normally stay empty before the run. It is only a temporary prefill/cache for the current OTP dialog, not a durable secret.
@@ -508,10 +508,12 @@ See `references/openai-clawemail-verification-nondelivery-2026-05-09.md` for the
See `references/openai-add-phone-5sim-sms-activation-2026-05-10.md` for the phone-verification continuation where OpenAI `add-phone` required SMS, BOSS corrected that WhatsApp receive channels must not be used, and 5sim `activation/*/openai` orders plus visible country selection were identified as the right class of workflow.
See `references/openai-phone-verification-5sim-sms-2026-05-10.md` for the later detailed retry notes: follow configured country/default `vietnam`, cancel failed 5sim orders before buying new ones, treat OpenAI `无法向此电话号码发送验证码` as a number/provider rejection, and avoid CDP-only mutations that desync the React country selector back to `美国 (+1)`.
See `references/openai-phone-verification-5sim-sms-2026-05-10.md` for early detailed retry notes: cancel failed 5sim orders before buying new ones, treat OpenAI `无法向此电话号码发送验证码` as a number/provider rejection, and avoid CDP-only mutations that desync the React country selector back to `美国 (+1)`. **Do not copy the early Vietnam-default assumption when `FIVE_SIM_COUNTRY_ORDER` is set; use the corrected precedence in `references/openai-phone-verification-country-order-account-deactivated-2026-05-10.md`.**
See `references/openai-add-phone-1083-vietnam-resend-2026-05-10.md` for the follow-up test requested by BOSS: switch browser proxy to `socks5://192.168.2.8:1083`, regenerate Codex2API OAuth URL from the origin API when the auth session expires, continue with configured-country `vietnam` SMS activation, and for non-rejected numbers poll 5sim then click `重新发送` before canceling/retrying.
See `references/openai-phone-verification-country-order-account-deactivated-2026-05-10.md` for the correction that `FIVE_SIM_COUNTRY_ORDER` (for example `argentina,netherlands,indonesia`) takes precedence over empty `FIVE_SIM_COUNTRY_ID` and default `vietnam`, plus the recovery rule that `account_deactivated` during email verification ends phone attempts for that mailbox.
## Verification Checklist
- [ ] Config file exists at `~/.hermes/env/codex_oauth_onboarding.env` with mode-specific fields.
@@ -0,0 +1,152 @@
# Codex OAuth onboarding: token-lite automation flow (2026-05-10)
Goal: future runs should be script-driven and low-token. The agent should not paste every browser/5sim/mail intermediate state into chat. Use compact status lines, local logs, screenshots only on blockers, and one final summary.
## Core state machine
Use one orchestrator script that owns the flow and writes structured JSONL logs under a run directory, e.g.
```text
~/.Hermes/workspace/runs/codex-oauth/<run_id>/
run.json
events.jsonl
screenshots/
cdp-targets.json
fivesim-orders.jsonl
```
Recommended phases:
1. `preflight`
- Read `~/.hermes/env/codex_oauth_onboarding.env` once.
- Redact secrets in all printed output.
- Validate selected target mode, Codex2API origin/admin key, ClawEmail profile, browser proxy, and 5sim settings.
- Verify proxy TCP + exit IP with short timeouts.
2. `browser_start`
- Kill only the dedicated CDP Chrome process for port 9223.
- Start visible Chrome with a dedicated profile and `--proxy-server`.
- Verify CDP `/json/version` and browser target.
3. `oauth_start`
- Generate a fresh target OAuth URL via target API.
- For Codex2API, derive API origin if `CODEX2API_URL` includes `/admin/accounts`.
- Open URL through CDP `/json/new`.
4. `email_login`
- Submit ClawEmail address.
- Poll `mail-cli --profile codex-current mail list --fid 1 --limit 50 --json`.
- Select latest OpenAI code by timestamp, not message-list order.
- Fill code through CDP with React-safe value setter.
- If OpenAI returns `account_deactivated`, stop the current account and mark run failed before buying 5sim numbers.
5. `phone_verify`
- Enter only if URL/text indicates `add-phone`.
- Country priority: `FIVE_SIM_COUNTRY_ORDER` -> `FIVE_SIM_COUNTRY_ID` -> `vietnam` fallback only if both empty.
- For each country, rotate up to `PHONE_VERIFICATION_REPLACEMENT_LIMIT` numbers.
- Before buying/submitting, set visible OpenAI country selector to the intended country and verify it.
- Buy SMS activation: `/v1/user/buy/activation/{country}/{operator}/openai`.
- Submit local phone digits by visible paste/click or React-safe setter + visible submit.
- If direct rejection (`无法向此电话号码发送验证码`), cancel activation and rotate.
- If accepted/no direct rejection, poll `/v1/user/check/{activationId}`; if no SMS, click `重新发送`, poll again, retry resend at least once, then cancel/rotate.
6. `oauth_callback`
- After phone/email success, continue consent screen if present.
- Capture/submit callback to target API.
- Verify target platform shows account/session.
7. `cleanup/report`
- Cancel any active 5sim order on failure.
- Save screenshot on blocker.
- Final response includes: phase, outcome, account email, target session id (safe), current URL class, orders tried (activation IDs + country + outcome), screenshot path if blocker. No secrets/callback codes.
## Token-minimizing output rules
- Do not stream full DOM/body text to chat.
- Scripts should print compact JSON or single-line events, e.g.
```json
{"phase":"phone_verify","country":"argentina","attempt":1,"activation_id":"100...","result":"direct_reject"}
{"phase":"phone_verify","country":"netherlands","attempt":2,"result":"accepted_no_sms_after_resend"}
{"phase":"email_login","result":"account_deactivated","screenshot":"/tmp/...png"}
```
- Store verbose outputs in files and only surface paths.
- Only send screenshots for blockers or when BOSS asks.
- Use `execute_code` or one `terminal` script for loops; avoid repeated tool calls for each 5sim poll.
- Avoid `browser_snapshot`/full DOM dumps unless diagnosing a selector bug.
- Prefer CDP helper functions that return exact state classes:
- `login_page`
- `email_code_page`
- `add_phone_page`
- `phone_code_page`
- `account_deactivated`
- `cloudflare_or_challenge`
- `unknown`
## Correct 5sim country handling
Current env can include both:
```bash
FIVE_SIM_COUNTRY_ORDER=argentina,netherlands,indonesia
FIVE_SIM_COUNTRY_ID=
PHONE_VERIFICATION_REPLACEMENT_LIMIT=3
```
Do **not** ignore `FIVE_SIM_COUNTRY_ORDER`. Country order should be:
1. Split non-empty `FIVE_SIM_COUNTRY_ORDER` by comma and trim.
2. Else use non-empty `FIVE_SIM_COUNTRY_ID`.
3. Else fallback to original extension default `vietnam`.
Known OpenAI Chinese labels/prefixes used in visible country selector:
```text
argentina 阿根廷 (+54)
netherlands 荷兰 (+31)
indonesia 印度尼西亚 (+62)
vietnam 越南 (+84)
england 英国 (+44)
japan 日本 (+81)
germany 德国 (+49)
```
## Known pitfalls from completed run
- Proxy switch can invalidate OpenAI auth session; always be ready to regenerate target OAuth URL and redo email login.
- `CODEX2API_URL` may be a web admin page; use origin for API calls.
- CDP-only phone/country changes can desync React state and submit the number under `美国 (+1)`. Verify visible selector before buying/submitting.
- A page returning `account_deactivated` after email-code submit means the current account is no longer usable; do not spend 5sim numbers on it.
- Clicking/resending on a non-rejected phone may leave the flow at `电子邮件地址已验证`; if continuing same account, reopen current OAuth URL and redo login. If `account_deactivated`, stop.
- Chrome stable may require visible profile/UI; keep a visible browser for auth pages.
## Suggested implementation artifact
Create or maintain a reusable local script, e.g.
```text
/Users/chick/.Hermes/workspace/scripts/codex_oauth_onboarding_runner.py
```
It should accept flags:
```bash
python3 codex_oauth_onboarding_runner.py \
--env ~/.hermes/env/codex_oauth_onboarding.env \
--mode codex2api \
--proxy-from-env \
--run-once \
--max-phone-countries-from-env \
--screenshot-on-blocker
```
The script should be idempotent enough to resume from `email_login` or `phone_verify` if the browser is already open, but should default to a clean profile per run.
## Final report template
```text
结果:失败/成功
阶段:email_login / phone_verify / oauth_callback
邮箱:...
代理:socks5://192.168.2.8:1085(出口 IP: ...
目标:Codex2API session_id=...
手机验证:argentina[...], netherlands[...], indonesia[...]
阻塞原因:account_deactivated / no_sms_after_resend / direct_reject_all / challenge
截图:MEDIA:/path/to/screenshot.png
下一步:...
```
@@ -0,0 +1,94 @@
# OpenAI phone verification: configured 5sim country order and account-deactivated recovery (2026-05-10)
## Why this note exists
During a Codex2API OAuth onboarding continuation, the agent mistakenly looked only at `FIVE_SIM_COUNTRY_ID=` and treated the empty value as permission to fall back to the original extension default `vietnam`. BOSS corrected that the active configuration already had a country allow/order list:
```bash
FIVE_SIM_COUNTRY_ORDER=argentina,netherlands,indonesia
FIVE_SIM_COUNTRY_ID=
FIVE_SIM_COUNTRY_FALLBACK=
PHONE_VERIFICATION_REPLACEMENT_LIMIT=3
```
Future runs must treat `FIVE_SIM_COUNTRY_ORDER` as the primary configuration, not as a secondary hint.
## Correct country precedence
Use this order when buying 5sim SMS activation numbers for OpenAI `add-phone`:
1. If `FIVE_SIM_COUNTRY_ORDER` is non-empty, split it by comma and try countries in that order.
2. For each country in the order, try up to `PHONE_VERIFICATION_REPLACEMENT_LIMIT` replacement numbers unless BOSS overrides.
3. Only if `FIVE_SIM_COUNTRY_ORDER` is empty, use `FIVE_SIM_COUNTRY_ID`.
4. Only if both are empty, use the extension default such as `vietnam`.
5. Do not silently switch outside the configured order; ask BOSS or update config first.
For the observed config, the correct sequence was:
```text
argentina -> netherlands -> indonesia
```
not Vietnam.
## 5sim/OpenAI retry behavior
- Use SMS activation only, not WhatsApp:
- `GET /v1/user/buy/activation/{country}/{operator}/openai`
- `GET /v1/user/check/{activationId}`
- `GET /v1/user/cancel/{activationId}` when abandoning.
- For direct OpenAI rejection (`无法向此电话号码发送验证码。请稍后重试或使用其他号码。`): cancel that activation and buy a new number in the same configured country until the per-country replacement limit is hit.
- For a number that is not directly rejected: keep the activation, poll 5sim, click OpenAI `重新发送` if no SMS arrives, then poll again before canceling.
- Always confirm the visible OpenAI country selector matches the 5sim country before buying/submitting. CDP-only field mutation can desync React state and cause the page to parse a non-US number as `美国 (+1)`.
## Country selector examples from the active config
Expected visible country labels:
```text
argentina -> 阿根廷 (+54)
netherlands -> 荷兰 (+31)
indonesia -> 印度尼西亚 (+62)
```
Use visible UI selection or a DOM click on the real option element. Do not mutate hidden state only.
## Email verification recovery caveat
After repeated phone-number attempts, canceling a non-rejected activation, or changing proxy, OpenAI may settle back to:
```text
https://auth.openai.com/email-verification
电子邮件地址已验证
```
To continue, re-open a fresh Codex2API OAuth URL and complete email login again. When polling ClawEmail:
- Sort OpenAI mail candidates by message date; do not trust list order alone.
- Confirm the code visible in the page input if submission stalls.
- If `重新发送电子邮件` does not produce a new code and the page returns:
```text
糟糕,出错了!
验证过程中出错 (account_deactivated)。请重试。
```
then the current OpenAI account is deactivated and cannot continue to phone verification. Stop trying phone numbers for that email; start a new ClawEmail sub-mailbox/register flow and then apply the configured country order above.
## 1085 rollback confirmation
BOSS later requested switching the browser proxy back from `socks5://192.168.2.8:1083` to `socks5://192.168.2.8:1085` and retrying the same mailbox before giving up. The correct check sequence was:
1. Patch `BROWSER_PROXY_SERVER=socks5://192.168.2.8:1085` in `~/.hermes/env/codex_oauth_onboarding.env`.
2. Verify the LAN proxy before browser work:
- `nc -vz -w 2 192.168.2.8 1085`
- `curl --proxy socks5h://192.168.2.8:1085 --max-time 8 https://api.ipify.org?format=json`
3. Kill the existing `remote-debugging-port=9223` Chrome and relaunch the isolated profile with `--proxy-server=socks5://192.168.2.8:1085`.
4. Generate a fresh Codex2API OAuth URL from the API origin, not from `/admin/accounts`.
5. Repeat email login and submit the newest OpenAI code.
Observed result: `1085` was reachable and showed exit IP `13.158.3.8`, but the same mailbox still returned `account_deactivated` after submitting the newest email code. This means proxy rollback did not rescue the mailbox; do not proceed to 5sim for an account that is already deactivated.
## Session outcome
The current account reached `account_deactivated` before the corrected country-order phone test could proceed. The learned correction is still valid: future runs must honor `FIVE_SIM_COUNTRY_ORDER=argentina,netherlands,indonesia` before considering defaults.