Browser uses Clash. Codex / Claude still get 403 country not supported.
Nine times out of ten, NO_PROXY contains openai.com. curl still goes through the proxy; Python and Go go direct. whichproxy prints that disagreement. It never sends a request.
Warning
Never put openai.com, .openai.com, api.openai.com, auth.openai.com, chatgpt.com, or .chatgpt.com in NO_PROXY.
That is what makes Codex / ChatGPT token exchange skip Clash.
Keep NO_PROXY local only, for example:
localhost,127.0.0.1,::1
Python 3.10+, zero dependencies. Not on PyPI yet:
pip install git+https://github.com/hc-ui/whichproxy.gitThen run whichproxy doctor (or clinic). It prints a Chinese diagnosis plus copy-pasteable fix commands. It does not write environment variables.
whichproxy doctor --copy # 诊断并复制本窗口修复命令
whichproxy ports # 看 7897 / 15721 谁在听
whichproxy suggest -o fix.ps1
. .\fix.ps1 # 只改当前窗口
Bad NO_PROXY (the usual Windows + Clash footgun):
$env:HTTP_PROXY="http://127.0.0.1:7897"$env:HTTPS_PROXY="http://127.0.0.1:7897"$env:NO_PROXY="localhost,127.0.0.1,openai.com"
whichproxy api.openai.comPOSIX:
HTTP_PROXY=http://127.0.0.1:7897 \
HTTPS_PROXY=http://127.0.0.1:7897 \
NO_PROXY=localhost,127.0.0.1,openai.com \
whichproxy api.openai.comapi.openai.com
HTTP_PROXY=http://127.0.0.1:7897
NO_PROXY=localhost,127.0.0.1,openai.com
curl PROXY (via http://127.0.0.1:7897)
python DIRECT (urllib proxy_bypass)
go DIRECT (suffix openai.com)
curl only exact-matches a bare token; Python and Go treat openai.com as a suffix. The host is marked DISAGREE.
| Command | What it does |
|---|---|
whichproxy HOST [HOST...] | Route each host would take under curl / python / go |
whichproxy env | Print proxy-related environment variables |
whichproxy doctor | Check well-known AI hosts and warn about dangerous NO_PROXY entries |
whichproxy suggest | Print a safe NO_PROXY (does not write env) |
whichproxy --json | Same results, machine-readable |
env / doctor read HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY (plus lowercase aliases).
Same NO_PROXY list, three models. The leading-dot difference is the whole point:
| Model | How it matches | openai.com (no dot) | .openai.com (leading dot) |
|---|---|---|---|
| curl | * = all hosts; no-dot = exact token; leading . = subdomains only | only openai.com — notapi.openai.com | api.openai.com, notopenai.com itself |
| python | urllib.request.proxy_bypass_environment — suffix match | openai.comandapi.openai.com | same urllib rules (leading . is stripped, then suffix) |
| go | * = all; no-dot = that host and subdomains; leading . = subdomains only; CIDR for IPs | openai.comandapi.openai.com | api.openai.com, notopenai.com itself |
Disagreement is reported as DISAGREE, with each model's reason.
- Local only. Reads your environment. No network calls, no API keys, nothing uploaded.
- If a proxy URL contains
user:password, the password is redacted in output.