--- name: codex-oauth-plus-onboarding description: "Low-token, stable workflow for BOSS's Codex/OpenAI OAuth onboarding: ClawEmail or 2925 mailbox signup, 5sim/Luban SMS phone verification, and callback import into CPA/Codex2API/SUB2API." version: 2.0.0 author: Hermes Agent license: MIT metadata: hermes: tags: [codex, oauth, clawemail, 5sim, cpa, codex2api, sub2api, browser-automation] related_skills: [clawemail-operations, browser-extension-storage-audit] --- # Codex OAuth + CPA/Codex2API Onboarding — low-token runbook Use when BOSS asks to register/login an OpenAI/Codex OAuth account, verify it via ClawEmail **or 2925 Webmail account-pool aliases**, possibly complete SMS phone verification via 5sim/Luban, then import the OAuth callback into **CPA / CLI Proxy API**, Codex2API, or SUB2API. Safety: BOSS-owned infra/accounts only. Stop for CAPTCHA/security challenge/payment confirmation unless BOSS explicitly approves the next step. Never reveal API keys, passwords, proxy URLs, full phone numbers, OAuth `code=`, SMS/email codes, admin keys, or tokens; report `[REDACTED]`. ## 0. Required companion skill Before ClawEmail work, load: ```text clawemail-operations ``` ## 1. Low-token operating mode Prefer script-driven runs that emit compact JSONL. Only send screenshots or long logs when a gate fails. ### Tool/reporting rules - Read secrets only from `~/.hermes/env/codex_oauth_onboarding.env`; do not paste them into chat. - **Do not blindly `source` this env file in shell**: values such as `CHROME_BIN=/Applications/Google Chrome.app/...` may contain spaces and break `source` if unquoted. For probes/runners, parse `KEY=VALUE` safely in Python or require quoted values before shell sourcing. - Reports should contain: phase, account email, country slug, activation id, high-level status, HTTP status, and redacted error class. - After any tool-driven step, always return a visible status/result to BOSS. Never end with an empty response after tool calls; if work continues, state the current verified checkpoint and next action. - Avoid dumping HTML, callback URLs, headers, full 5sim responses, or full phone numbers. - Save detailed evidence to `references/` or local logs; final response stays concise. ## 2. Environment constants for BOSS ```text env: ~/.hermes/env/codex_oauth_onboarding.env workspace: /Users/chick/.Hermes/workspace OAuth Chrome CDP default: 9223 ClawEmail Dashboard CDP default: 9224 OAuth profile root: /Users/chick/.Hermes/workspace/browser-profiles/codex-oauth Dashboard profile: /Users/chick/.Hermes/workspace/browser-profiles/clawemail-dashboard-persistent Preferred browser proxy when unspecified: socks5://192.168.2.8:1085 Chrome: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome ``` Use visible Google Chrome for OpenAI/Auth pages. Do not use BOSS's personal browser profile. ## 3. Hard gates — do not skip 1. **Target gate**: exactly one target mode: `cpa`, `codex2api`, or `sub2api`. No default if env is empty. 2. **Password gate**: `CUSTOM_PASSWORD` must be set unless BOSS approves generating one. 3. **Proxy gate**: verify proxy TCP and OpenAI reachability before browser flow. 4. **Mailbox gate**: if using ClawEmail, new sub-mailbox must have external receive enabled before OpenAI signup; if using 2925, generated alias and visible Webmail account must be bound to the same selected pool account before clearing inbox or polling code. 5. **OpenAI identity gate before phone**: only buy 5sim if active page is a usable `https://auth.openai.com/add-phone` for a non-deactivated account. 6. **Country gate before phone buy/submit**: OpenAI select value and visible dial code must match target country before buying and again before clicking Continue. 7. **SMS-only gate**: if OpenAI resend channel probes as WhatsApp, do not click it; cancel/rotate under SMS-only policy. 8. **Callback state gate**: callback `state` must match generated OAuth state when known. 9. **Payment gate**: any Plus/GoPay paid action needs explicit BOSS confirmation. ## 4. Recommended decision tree ```text Need OAuth import? ├─ Target CPA available? → prefer CPA (most reliable after 2026-05-10 success) ├─ Codex2API requested? → try Codex2API; if token exchange returns unsupported_country_region_territory, switch/fix backend proxy or use CPA └─ SUB2API requested? → use original extension/content-script path Need new OpenAI account? ├─ Prefer 2925 if ClawEmail instability is a concern: pick pool account → generate bound 2925 alias → login same Webmail account → clear inbox → poll/delete code ├─ If using ClawEmail: create sub-mailbox with 8 lowercase letters ├─ Enable ClawEmail external receive via Dashboard API before submit ├─ Submit email/password, poll exact mailbox code └─ If phone page appears → 5sim/Luban SMS flow Already phone-verified account? └─ Generate fresh target OAuth URL, login/consent, submit callback to target ``` ## 5. ClawEmail sub-mailbox gate BOSS preference: per-run ClawEmail prefix is **exactly 8 lowercase letters** with no `codex` prefix, e.g. `chickliu.ktqcxzux@claw.163.com`. After creation, enable external receive. Observed Dashboard API base: ```text https://claw.163.com/mailserv-claw-dashboard/api/v1 ``` Do not use relative `/api/v1/...` if it returns public ClawEmail HTML. Working sequence: `GET /workspaces` -> `GET /mailboxes?workspaceId=` -> find the target sub-mailbox id -> `POST /mailboxes/comm-settings?id=` with the payload above. If controlling the persistent Dashboard Chrome on CDP `9224` fails with `Handshake status 403 Forbidden ... remote-allow-origins`, use `websocket.create_connection(ws_url, suppress_origin=True)` or restart Chrome with `--remote-allow-origins=*`. Required final values: ```json {"commLevel":2,"extReceiveType":1,"extSendType":0} ``` Use Dashboard persistent Chrome/profile for master login, not the disposable OAuth profile. If Dashboard login needs a master QQ mailbox code and the agent cannot retrieve it, ask BOSS. If automating through CDP, Chrome may reject WebSocket Origin unless launched with `--remote-allow-origins=*`; Python `websocket-client` can connect with `suppress_origin=True`. If relative `/api/v1/workspaces` returns raw/empty data, inspect Dashboard resource URLs and try the full `https://claw.163.com/mailserv-claw-dashboard/api/v1/...` base before diagnosing mailbox absence; see `clawemail-operations` reference `references/clawemail-dashboard-cdp-origin-and-api-base-pitfalls-2026-05-10.md`. Key references: - `references/clawemail-external-receive-gate-openai-2026-05-10.md` - `references/clawemail-random-suffix-policy-2026-05.md` ## 6. OpenAI email/profile flow Minimal flow: ```text fresh OAuth URL / signup page submit ClawEmail sub-mailbox using real CDP input events, not only `input.value=` submit CUSTOM_PASSWORD using real CDP input events poll ClawEmail inbox/spam for OpenAI code using a mail-cli config/profile that points to the exact sub-mailbox submit code using real CDP input events fill profile/about-you if shown continue to phone or OAuth consent ``` OpenAI Auth can route a fresh sub-mailbox first to `log-in/password`; if the password attempt returns `Incorrect email address or password`, click visible `注册` / `Sign up` and continue on `create-account`. Navigation after code/password submit can close the inspected CDP target; reattach and inspect before retrying. `mail-cli` default profile may still point to `chickliu@claw.163.com`; it will not show mail delivered to `chickliu.@claw.163.com`. For sub-mailbox code polling, create a temp config/profile for that UID before `mail list`/`read body`; see `clawemail-operations` reference `references/submailbox-profile-polling-openai-code-2026-05-10.md`. Expected fields observed in 2026-05-11 run: ```text input[name=name] type=text required input[name=age] type=number required input[name=birthday] type=hidden ``` Set name and a valid numeric age explicitly with the native input value setter and dispatch `input`/`change`; optionally set hidden birthday consistently. Do not fill every input with the same generic text. If OpenAI returns `account_deactivated`, stop that account immediately and do not buy 5sim numbers. If login recovery hits `Incorrect email address or password`, `invalid_state`, stale email verification, or tab ambiguity, stop before buying phone numbers; recover account/page first. For existing accounts with password failure, the login-page `使用一次性验证码登录` path may send an email code, but `mail-cli` output is not guaranteed newest-first: parse/sort the `date` field and submit the newest OpenAI login mail. Do not use passwordless signup (`使用一次性验证码注册`) for an existing account; it can return `passwordless_signup_disabled`. If a fresh email-code submission returns `account_deactivated`, stop the account immediately even if phone verification previously succeeded. ## 7. 5sim SMS phone flow If login recovery hits `Incorrect email address or password`, `invalid_state`, stale email verification, or tab ambiguity, stop before buying phone numbers; recover account/page first. For existing accounts with password failure, the login-page `使用一次性验证码登录` path may send an email code, but `mail-cli` output is not guaranteed newest-first: parse/sort the `date` field and submit the newest OpenAI login mail. Do not use passwordless signup (`使用一次性验证码注册`) for an existing account; it can return `passwordless_signup_disabled`. If a fresh email-code submission returns `account_deactivated`, stop the account immediately even if phone verification previously succeeded. ## 6A. 2925 mailbox provider option Use this when ClawEmail accounts appear unstable and BOSS wants 2925 instead. The original extension treats 2925 as a class-level mail provider with two modes: ```text mail2925Mode=provide # preferred for replacing ClawEmail: generate 2925 aliases for OpenAI signup mail2925Mode=receive # registration email comes from elsewhere; 2925 only receives/polls mail ``` For `provide`, generate registration emails as `baseLocal + randomSuffix(6) + @2925.com`; **do not** use Gmail-style `+tag`. Example: `abc@2925.com -> abcxk39qz@2925.com`. Recommended gates for Hermes runners: 1. Use an account pool; format can mirror original extension imports: `email@2925.com----[REDACTED_PASSWORD]`. 2. Select an enabled, non-cooldown account with the oldest `lastUsedAt`; tie-break by email. 3. Open `https://2925.com/#/mailList` or login at `https://2925.com/login/`; on force relogin clear cookies for `2925.com`, `www.2925.com`, and `mail2.xiyouji.com`. 4. Verify the displayed mailbox email equals the selected 2925 account before polling. If mismatched, relogin to the selected account; do not poll the wrong mailbox. 5. Before requesting an OpenAI code, pre-clear or explicitly ignore stale OpenAI messages in 2925 Inbox/Junk/Deleted where possible. 6. Poll both `收件箱` and `垃圾箱` for signup/login codes; OpenAI/ChatGPT mail may be classified as junk. Open candidate mail, extract a strict 6-digit code, delete after read, return to mailbox. When checking 2925 for fresh mail, explicitly click the page's visible `刷新` button (and log `clicked=true`) before concluding no mail arrived; URL route reload / `Page.navigate` alone is not enough evidence for BOSS. - BOSS-corrected resend cadence: refresh 2925 `收件箱` + `垃圾箱` every ~15 seconds; after 3 consecutive no-code refresh rounds for the current alias, click OpenAI `重新发送电子邮件` once, then resume the same refresh cadence. If BOSS asks for “10 resends”, this means **10 blocks of `3 mailbox refresh rounds -> 1 OpenAI resend`**, not “resend first then poll” and not “resend then refresh once”. Use `scripts/mail2925_strict_resend_cadence.py` for this exact JSONL runner. Log each resend click. Do not wait indefinitely without resend, and do not buy SMS before this email gate passes. 7. Maintain `seenCodes` and `rejectedCodes`; if OpenAI rejects a code, request/resend and keep polling instead of reusing old mail. 8. Candidate mail/code must match the current generated alias/local-part (for example `chickliu2026rigm1r`) or an unambiguous current-run timestamp/window. Do **not** submit visible codes from older aliases in `垃圾箱` or `已删除`. 9. If BOSS asks to confirm the 2925 login visually, activate the actual Chrome target/window first and send both a CDP page screenshot and a macOS visible screenshot. A macOS `screencapture` without activating the 2925 Chrome target can capture the wrong app/window even when DOM reads prove 2925 is logged in. 10. When switching 2925 accounts, close stale OpenAI/auth tabs and force a clean 2925 login: clear cookies for `2925.com`/`www.2925.com`/`mail2.xiyouji.com` or start a fresh Chrome profile. Verify top-account again before polling. A live run stayed logged into the previous 2925 account until a fresh profile was used. 11. Detect `子邮箱已达上限` / `已达上限邮箱` / `子邮箱上限` / `邮箱已达上限`; mark the current 2925 account cooldown for 24h and switch accounts. If no account pool/next account exists, stop the flow. 11. Live-test caveat 2026-05-11: `chickliu2025...` and `chickliu2026...` OpenAI signup messages were not seen in Inbox; older OpenAI messages existed in Junk/Deleted, and submitting one stale visible code returned `代码不正确`. Stop before SMS purchase if no current-alias code is found. 12. 2026-05-11 strict resend test correction: if automation reports no 2925 OpenAI mail after `3 refresh + 1 resend` loops, first audit the scraper. A live run with `chickliu2030...`/`chickliu2031...` proved messages were present in Inbox but missed by route-based scraping. Correct method: click the left `收件箱` nav item (do not rely on `#/mailList?type=收件箱`), click toolbar `刷新`, read `table.maillist-table tr`, inspect hidden tooltip/title/textContent for the generated alias in OpenAI bounce addresses such as `...-chickliu2031som6d8=2925.com@tm1.openai.com`, then open the newest matching row and extract the 6-digit code near `输入此临时验证码以继续:`. If OpenAI rejects an old code, resend once and select the newest row. Reference: `references/mail2925-correct-inbox-scraping-openai-code-2026-05-11.md`. 13. Session details: `references/mail2925-explicit-refresh-and-visible-confirmation-2026-05-11.md`, `references/mail2925-resend-cadence-and-browser-hygiene-2026-05-11.md`. - For 2925 alias polling, use `scripts/mail2925_openai_code_scraper.py` for OpenAI/ChatGPT verification codes. It implements the corrected Webmail flow: click left `收件箱`, click toolbar `刷新`, read `table.maillist-table tr`, inspect hidden tooltip/bounce text for the alias local-part, open the newest matching row, and write the raw 6-digit code only to the requested output file. Use `scripts/mail2925_poll_alias_code.py` only when its implementation is confirmed to follow the same DOM rules; older list/route-based polling can produce false negatives. Reference: `references/2925-mail-provider-original-extension-rules-2026-05-11.md` and `references/mail2925-account-pool-integration-2026-05-11.md`. BOSS-provided pool is stored locally at `~/.hermes/secrets/mail2925_accounts.txt` with mode `0600` (20 accounts, `chickliu2021@2925.com` through `chickliu2040@2925.com`). Never print or commit raw passwords. Use `scripts/mail2925_pool.py` for account rotation and alias generation. Hard invariant: generated alias local-part must start with the selected account local-part, and the displayed 2925 mailbox must equal the selected account before polling code. If mismatch, clear 2925 cookies and force-login the selected account; never read code from another account's mailbox. ## 7. 5sim SMS phone flow Use SMS activation provider selected by `PHONE_SMS_PROVIDER`: ```text 5sim (default): PHONE_SMS_PROVIDER=5sim Luban SMS: PHONE_SMS_PROVIDER=luban ``` ### Luban SMS provider Docs: `https://lubansms.com/api_docs/`. All endpoints are GET under: ```text https://lubansms.com/v2/api ``` Env: ```text LUBAN_SMS_API_KEY=[REDACTED] LUBAN_SMS_API_BASE=https://lubansms.com/v2/api LUBAN_SMS_SERVICE_QUERY=OpenAI LUBAN_SMS_LANGUAGE=en ``` Important API quirk: requests without a browser-like `User-Agent` may return HTTP 403. Use a Chrome UA. Core endpoints: ```text BALANCE: /getBalance?apikey=... SERVICES: /List?apikey=...&country=&service=OpenAI&language=en&page=1 BUY: /getNumber?apikey=...&service_id= CHECK: /getSms?apikey=...&request_id= CANCEL: /setStatus?apikey=...&request_id=&status=reject HISTORY: /smsHistory?apikey=...&language=en&page=1 ``` Observed OpenAI service lookup returns entries such as `OpenAI / ChatGpt` and `OpenAI`; cheapest current match can be resolved with: ```bash /Users/chick/homebrew/opt/python@3.11/bin/python3.11 \ /Users/chick/.hermes/skills/software-development/codex-oauth-plus-onboarding/scripts/luban_sms.py \ resolve --service OpenAI --language en --pages 5 ``` Verified 2026-05-11 with BOSS key: balance API OK, balance `2.000`; service list OK. Do not print the key. The helper script is at `scripts/luban_sms.py`. Integration/verification policy: - First validate Luban with `balance` and `resolve` only; do not buy a number while merely adding the provider. - Luban `/List` output is not a fixed country matrix; resolve `service_id` dynamically from `service=OpenAI`/`OpenAI / ChatGpt`, then pick the cheapest usable match unless BOSS asks for a specific country/provider. - Treat Luban `request_id` as the activation id. Poll `/getSms`; `msg=wait` means keep polling, `msg=success` plus `sms_code` means submit a strict 6-digit code, and `wrong_status` means the request expired/closed. - **Hold Luban orders for at least 3 minutes before release when BOSS asks for real SMS-delivery testing.** A too-short release window can return provider errors such as `停用失败,该供应商需要在两分钟后停止`; wait from purchase timestamp, then call `/setStatus?...&status=reject`. - Only poll Luban SMS after OpenAI actually lands on `phone-verification` / a visible SMS-code page. If submit remains on `add-phone` without explicit rejection, classify it as `submit_no_sms_page`, hold the order for the configured minimum window, release it, navigate back to `add-phone`, and continue; do not report it as a confirmed SMS-page no-delivery attempt. - Release failed/rejected Luban numbers with `/setStatus?...&status=reject`; Luban has no 5sim-style `finish`, so a successful code submission is a local no-op finish in the runner. Some Luban suppliers reject immediate cancellation with messages like `停用失败,该供应商需要在两分钟后停止`; treat this as a deferred-release state, wait ~2 minutes, re-check `/getSms`, then retry `setStatus&status=reject` before starting another spend-heavy run. - Store the key only in `~/.hermes/env/codex_oauth_onboarding.env` as `LUBAN_SMS_API_KEY`; never echo it in command output, logs, or chat. - Details: `references/luban-sms-provider-integration-2026-05-11.md`. - Session recap for 2925 account-pool, Luban UA 403, and empty-response guardrail: `references/session-learnings-2925-luban-reporting-2026-05-11.md`. - Provider abstraction lessons: `references/sms-provider-abstraction-and-luban-2026-05-11.md`. - Luban UK live test 2026-05-11: with `chickliu2031som6d8@2925.com` email-verified and on OpenAI `add-phone`, 10 United Kingdom/England OpenAI orders using service_id `537935` were bought and held for ~190s each before release. Attempts 1-9 reached `phone-verification` and were not immediately rejected by OpenAI, but Luban `getSms` stayed `msg=wait`; attempt 10 stayed on `add-phone` after submit and should be classified as `submit_no_sms_page`, not a confirmed SMS page. Summary: bought=10, SMS received=0, all orders released successfully. Reference: `references/luban-uk-openai-10-attempts-no-sms-2026-05-11.md`. - Luban Chile live test 2026-05-11: service_id `508058` resolved for Chile/OpenAI at cost `0.05`. First number passed the `CL` / `智利` country gate and was bought successfully, but OpenAI stayed on `add-phone` and was classified as immediate reject; the order was held for the 3-minute minimum window and released successfully. Summary: bought=1, rejected_by_openai=1, SMS received=0. Reference: `references/luban-chile-openai-immediate-reject-2026-05-11.md`. - OpenAI phone-code submit audit 2026-05-11: current phone-verification DOM uses a single input `name=code`, `autocomplete=one-time-code`, `maxLength=6`. Reuse the proven 5sim Vietnam submit method: native value setter + `InputEvent('input', {inputType:'insertText', data: code})` + `change` + exact `继续` click. The Luban free-country runner's generic plain `Event('input')` submit may not update React state; classify received-but-not-accepted codes as `sms_submit_failed`, not success. Reference/helper: `references/openai-phone-code-submit-method-audit-2026-05-11.md`, `scripts/submit_openai_phone_code_corrected.py`. - Luban one-by-one runner review/fix 2026-05-11: after reviewing the 5sim Vietnam success and 335-activation Indonesia success records, the previous ad-hoc Luban runner had defects: continued after OpenAI `糟糕,出错了!`/Bad Request pages, shallow Luban release retries, possible non-React-safe code submit, and under-logged WhatsApp/SMS channel ambiguity. Use `scripts/luban_one_by_one_phone_verify.py` instead of `/tmp/luban_one_by_one_stop_on_sms.py`. It validates `add-phone` readiness before buying, stops on unrecoverable bad pages, loops Luban `reject` until success/already-released/wrong_status, submits codes with the proven `InputEvent` method, and exits while keeping page/order if `sms_submit_failed_hold_for_retry` occurs. Reference: `references/luban-one-by-one-runner-review-and-fixes-2026-05-11.md`. - For broad Luban testing, always log page channel text (`WhatsApp` vs SMS). If page says WhatsApp but provider returns SMS-style code, treat submit failures as ambiguous until provider raw fields and OpenAI error text are inspected. - Luban unrestricted live test 2026-05-11: 10 actual bought numbers across Brazil, Philippines, Argentina, Indonesia, Vietnam, Netherlands, Malaysia, Mexico, France, and Brazil again; max 2 per country, held/released per 3-minute policy. Results: 7 OpenAI immediate rejects, 2 SMS timeouts after reaching `phone-verification`, and 1 France SMS delivery. France `service_id=735664` delivered in ~11s, but code submission left OpenAI on `phone-verification`; classify as `sms_submit_failed`, not success. Do not set `success_request_id` unless OpenAI leaves `phone-verification`; see `references/luban-unrestricted-openai-10-buys-france-sms-submit-failed-2026-05-11.md`. - Deprecated Luban one-by-one runner note 2026-05-11: `/tmp/luban_one_by_one_stop_on_sms.py` / `scripts/luban_one_by_one_stop_on_sms.py` was an earlier ad-hoc version. It proved the stop-on-SMS idea, but it was superseded by `scripts/luban_one_by_one_phone_verify.py` because the old version could continue after OpenAI bad-page states and had shallow release retry. Do not use the old script for new runs; use the optimized script above. - Before continuing after a Luban phone run, confirm OpenAI `add-phone` is not in `糟糕,出错了!` state and that the country selector is present; `countryValue:null` / `countryText:null` is a page-state failure, not evidence that a country is unsupported. - Session review 2026-05-11: class-level lessons from the 2925 + Luban OpenAI onboarding run are summarized in `references/session-review-2925-luban-openai-onboarding-2026-05-11.md`; use it to avoid route-based 2925 false negatives, distinguish OpenAI phone acceptance from SMS delivery, and preserve Luban 3-minute release timing. If using the long-running runner: ```bash PHONE_SMS_PROVIDER=luban START_USED_NUMBERS= MAX_NEW_NUMBERS= CDP_PORT= \ /Users/chick/homebrew/opt/python@3.11/bin/python3.11 \ /Users/chick/.hermes/skills/software-development/codex-oauth-plus-onboarding/scripts/phone_verify_until_success.py ``` 5sim legacy API: ```text PHONE_SMS_PROVIDER=5sim FIVE_SIM_OPERATOR=any FIVE_SIM_PRODUCT=openai FIVE_SIM_SERVICE=openai BUY: /v1/user/buy/activation/{country}/any/openai CHECK: /v1/user/check/{activation_id} FINISH: /v1/user/finish/{activation_id} CANCEL: /v1/user/cancel/{activation_id} ``` Country precedence for configured runs: ```text FIVE_SIM_COUNTRY_ORDER > FIVE_SIM_COUNTRY_ID > vietnam fallback only if both empty ``` For free-country exploration, ignore stale configured order if BOSS explicitly says unrestricted. Count only real activations with ids toward the budget. `no free phones`, pre-buy country-gate errors, CDP errors, or OAuth recovery failures are not attempts. Known mappings: ```text vietnam -> VN -> 越南 (+84) -> 84 argentina -> AR -> 阿根廷 (+54) -> 54 netherlands -> NL -> 荷兰 (+31) -> 31 indonesia -> ID -> 印度尼西亚 (+62) -> 62 italy -> IT -> 意大利 (+39) -> 39 poland -> PL -> 波兰 (+48) -> 48 spain -> ES -> 西班牙 (+34) -> 34 england -> GB -> 英国 (+44) -> 44 chile -> CL -> 智利 (+56) -> 56 portugal -> PT -> 葡萄牙 (+351) -> 351 germany -> DE -> 德国 (+49) -> 49 romania -> RO -> 罗马尼亚 (+40) -> 40 ``` Phone handling: ```text OpenAI immediate reject (`无法向此电话号码发送验证码`) or invalid-phone (`电话号码无效`) → cancel activation, rotate No immediate reject → poll 5sim Before resend → probe channel text/value/aria/title ├─ WhatsApp → cancel/rotate (SMS-only) └─ SMS/unknown → click resend, poll again SMS received → prefer a 6-digit code candidate for OpenAI phone verification; submit code, verify page leaves phone-verification, then finish activation Interrupted runner → inspect/cancel active RECEIVED activations immediately ``` If a broad SMS regex finds 4-8 digit candidates, prefer `(?` for CPA OAuth because it can produce OpenAI `unknown_error`, and use same-tab `Page.navigate` instead. If phone is already verified but CPA OAuth still fails with `Workspaces not found in client auth session`, `unknown_error`, `Incorrect email address or password`, `invalid_state`, or `passwordless_signup_disabled`, treat it as an Auth/session/credential problem, not an SMS/proxy problem; do not keep buying phone numbers for the same account. Multi-country / free-country runner stability: - Count only real 5sim activations with ids toward requested totals. `no free phones`, country-gate failures, page recovery failures, and CDP errors do not count. - Log compact JSONL phases: `activation_bought`, `phone_submitted`, `activation_cancel_rejected`, `sms_timeout_cancel`, `sms_received`, and final summary. This makes it possible to top up to an exact activation count after page interruptions. - If OpenAI reaches `phone-verification` but page text says the code was sent by **WhatsApp**, treat it as SMS-only failure: poll 5sim briefly if already purchased, cancel on zero SMS, then explicitly navigate back to `https://auth.openai.com/add-phone` before continuing; otherwise later country gates fail because the page remains on `phone-verification`. - For OpenAI SMS/email verification code extraction, prefer strict 6-digit extraction (`(? MAX_NEW_NUMBERS=300 CDP_PORT= \ /Users/chick/homebrew/opt/python@3.11/bin/python3.11 \ /Users/chick/.hermes/skills/software-development/codex-oauth-plus-onboarding/scripts/phone_verify_until_success.py \ | tee /tmp/phone_verify_until_success.log ``` The script emits `new_numbers` and `total_numbers`, persists `/tmp/phone_verify_until_success_state.json`, and keeps `/tmp/current_fivesim_activation_id.txt` updated for interruption cleanup. `MAX_NEW_NUMBERS=0` means unlimited; otherwise keep a safety cap unless BOSS explicitly approves unlimited spend. See `references/until-success-phone-runner-cumulative-accounting-2026-05-11.md`. Known result: free-country run succeeded with Vietnam SMS, activation `1006871765`, status `FINISHED`; OpenAI phone verification passed. See `references/openai-phone-free-country-vietnam-sms-success-codex2api-region-block-2026-05-10.md`. ## 8. CPA / CLI Proxy API — preferred callback target CPA succeeded where Codex2API failed. Use this when `CPA_VPS_URL` and `CPA_VPS_PASSWORD` are configured. Derive API origin: ```python origin = new URL(CPA_VPS_URL).origin # e.g. http://192.168.2.62:8317/management.html -> http://192.168.2.62:8317 ``` Headers, matching original extension: ```text Authorization: Bearer [CPA_VPS_PASSWORD] X-Management-Key: [CPA_VPS_PASSWORD] Accept: application/json Content-Type: application/json ``` Generate OAuth URL: ```text GET /v0/management/codex-auth-url # UI also uses: GET /v0/management/codex-auth-url?is_webui=true ``` Extract URL from `url|auth_url|authUrl|data.*`; extract state from payload or URL. In Chrome 147/CDP, opening this URL via `/json/new?...` can route to OpenAI `unknown_error`; prefer navigating the current intended auth tab with `Page.navigate` when retrying fresh CPA URLs. Submit callback: ```text POST /v0/management/oauth-callback {"provider":"codex","redirect_url":"http://localhost:1455/auth/callback?code=[REDACTED]&state=[REDACTED]"} ``` Verify: ```text GET /v0/management/get-auth-status?state= -> {"status":"ok"} GET /v0/management/auth-files -> codex--free.json active ``` OAuth recovery pitfalls after phone success: - Do not keep buying phone numbers once OpenAI has left `phone-verification`; report phone success separately from OAuth/CPA import status. - If Codex consent shows `Workspaces not found in client auth session`, the account/session lacks a usable ChatGPT workspace. Establish a valid ChatGPT web session/workspace first, then generate a **new** CPA OAuth URL; repeatedly clicking retry on the same consent error does not fix it. - If one-time-code login is needed after password login fails, request it from a fresh auth page/state. A stale password page may make `使用一次性验证码登录` return `invalid_state`; polling ClawEmail may still find a code, but submitting it on the stale error page will not recover. - If many fresh CPA URLs start returning OpenAI `/error` `unknown_error` despite normal URL parameters, close auth tabs/restart the automation Chrome and consider pausing/restarting CPA before generating another URL. Do **not** use root `/codex-auth-url` or `/api/auth/url`; they can 404 or return unrelated `amp upstream proxy not available`. References: - `references/cpa-original-extension-api-notes-2026-05-10.md` - `references/cpa-panel-codex-oauth-success-2026-05-10.md` ## 9. Codex2API target Generate URL: ```text POST /api/admin/oauth/generate-auth-url Header: X-Admin-Key: [CODEX2API_ADMIN_KEY] Body: {} ``` Expected response contains: ```json {"auth_url":"...","session_id":"..."} ``` Exchange callback: ```text POST /api/admin/oauth/exchange-code Header: X-Admin-Key: [CODEX2API_ADMIN_KEY] {"session_id":"...","code":"[REDACTED]","state":"..."} ``` If callback/token exchange returns `unsupported_country_region_territory`, browser-side OAuth is not the issue. The backend token exchange path needs a supported outbound proxy or use CPA. ## 10. SUB2API target Use original extension/content-script route for SUB2API panel login, group, proxy, priority, and callback exchange. Do not invent direct API endpoints unless re-confirmed from source/UI. ## 11. Browser/CDP hygiene Before fresh OAuth recovery: ```text close stale auth.openai.com / phone-verification / localhost callback tabs close or kill obsolete Chrome/CDP processes from prior attempts (especially old OpenAI 9228 tabs) when switching mailbox/account, so CDP target selection cannot land on stale auth state keep only the current intended target tab prefer same-tab Page.navigate for CPA OAuth URLs; avoid Chrome /json/new? when OpenAI starts returning unknown_error ``` Use visible Chrome with isolated profile. Chrome stable may ignore `--load-extension`; verify extension card/sidepanel if using the extension. Manual CDP flow is acceptable when BOSS only wants registration/OAuth tested; still follow all gates. When connecting to Chrome CDP with `websocket-client`, Chrome may reject the WebSocket handshake with `403 Forbidden` unless the client sends an allowed Origin or Chrome was launched with `--remote-allow-origins=*`. Prefer launching automation Chrome with `--remote-allow-origins=*`; for an already-running CDP instance, pass `origin='http://127.0.0.1:'` or a matching allowed origin to `websocket.create_connection(...)` before assuming CDP is down. ## 12. Plus / GoPay Default ordinary account test: ```text PLUS_MODE_ENABLED=false ``` If Plus is requested: ```text PLUS_PAYMENT_METHOD=gopay GOPAY_OTP_SOURCE=manual GOPAY_OTP empty by default ``` Stop immediately before paid confirmation and ask BOSS. ## 13. Verification checklist before final report - [ ] New mailbox external receive enabled and recorded. - [ ] OpenAI email verification completed or classified. - [ ] If phone appeared: all active 5sim orders are FINISHED or CANCELED. - [ ] If SMS succeeded: OpenAI left `phone-verification` page. - [ ] OAuth callback submitted to chosen target. - [ ] Target has active account/session/auth file. - [ ] Final report redacts secrets, callback codes, SMS/email codes, full phone numbers, API keys, passwords, proxy strings. - [ ] If new learning occurred, update this skill or a reference. ## 14. Evidence and history references Full pre-rewrite skill was archived at: ```text references/SKILL-full-before-low-token-rewrite-2026-05-10.md ``` High-value references: ```text references/env-parsing-and-cdp-origin-pitfalls-2026-05-10.md references/cpa-original-extension-api-notes-2026-05-10.md references/cpa-panel-codex-oauth-success-2026-05-10.md references/openai-phone-free-country-vietnam-sms-success-codex2api-region-block-2026-05-10.md references/openai-add-phone-strict-country-gate-2026-05-10.md references/openai-add-phone-free-country-runner-policy-2026-05-10.md references/openai-login-recovery-gate-before-phone-runs-2026-05-10.md references/openai-oauth-tab-cleanup-before-recovery-2026-05-10.md references/openai-phone-country-scheduler-cyclic-chunks-2026-05-10.md references/clawemail-external-receive-gate-openai-2026-05-10.md references/openai-add-phone-chile-no-free-phones-2026-05-10.md references/openai-add-phone-eu4-total20-real-activations-2026-05-10.md references/openai-vietnam-phone-rejects-proxy1085-1083-2026-05-10.md references/free-country-openai-phone-20-activation-failure-2026-05-11.md references/openai-phone-free-country-continuation-1083-2026-05-11.md references/openai-email-code-login-account-deactivated-2026-05-11.md references/official-extension-phone-update-2026-05-10.mdenai-email-code-login-account-deactivated-2026-05-11.md references/official-extension-phone-update-2026-05-10.md ``` ```