一行响应头
终端里的 Claude Code 登不上。浏览器给出的答案是「App unavailable in region」——看上去是个再清楚不过的翻墙问题。

它不是。真正的答案写在一行几乎没人会去看的响应头里,中间隔着四层互相掩盖的故障。
第一层:一把作废的钥匙
别看浏览器,先让 CLI 自己说话:
1 | claude -p "reply with exactly: OK" |
1 | Failed to authenticate. API Error: 401 OAuth access token has been revoked. |
Keychain 里躺着一份被吊销的 OAuth token,客户端只会拿它去刷新,刷不动就永远卡在这儿。重新登录即可——如果登得上的话。
第二层:七千条规则里的一个洞
重新登录,授权页正常,点下 Authorize,跳转,地区拦截。
去翻 Clash 的规则表:
1 | grep -niE 'claude|anthropic' clash-verge.yaml |
1 | 8870:- DOMAIN-SUFFIX,anthropic.com,GPT |
七千多条规则,claude.ai 和 claude.com 一条都没有。两个域名一路穿过整张表,落进最后的 MATCH,Others——而那个组的候选里躺着 DIRECT。
前后一下就对上了。api.anthropic.com 有规则,走代理,所以 CLI 能连上 API,报的是干净的 401 而不是超时;claude.com 没规则,直连出门,撞上地区墙。
补四条前置规则,确认它们进了生成配置的最前面。
问题依旧。
两条弯路
第一条:我怀疑浏览器缓存。 用另一个浏览器实测 claude.com,正常。同一台机器、同一个出口,两个浏览器两种结果——于是我断定是浏览器缓存了修复前那张拦截页。
错。这个判断建立在「两个浏览器走同一条路」这个从未验证过的假设上。更糟的是,我用来支撑它的证据——我的浏览器能打开——本身就是那个待解释的现象,不是解释。
第二条:我怀疑 IPv6 泄漏。 浏览器优先 IPv6、而 fake-ip 只覆盖 IPv4,这是个常见的漏法。两条命令:
1 | ifconfig | grep 'inet6' | grep -v 'fe80\|::1' # 空 |
本机没有全局 IPv6,域名也没有 AAAA 记录。排除,收工。
两条弯路的差别值得记一下:第二条是被证据干净否掉的,成本两条命令;第一条是我拿猜测当了结论,还基于它给出了一轮操作建议。
转折:去要终端里的那行字
我一直在分析浏览器截图。而真正的报错在终端里躺着:
1 | OAuth error: Request failed with status code 403 |
同时拿到了另一个浏览器的出口 IP——和我测到的一模一样。
弯路一当场作废。浏览器之间根本没有路径差异,差异在别的地方。
一行响应头
不要只看状态码。看头:
1 | curl -s -D - -o /dev/null https://claude.ai/api/hello |
1 | HTTP/2 403 |
响应体是 <title>Just a moment...</title>。Cloudflare 的托管质询。
补上完整的浏览器请求头再试——UA、Accept、Accept-Language、Sec-Fetch-Mode,一个不落。照样 403。拦的不是 User-Agent,是 IP 本身。
机制到这儿就全亮了:
浏览器会执行 JS,能把质询解掉,所以网页打得开。CLI 用的是 Node 的 HTTP 客户端,不执行 JS,解不了,直接吃 403。
同一个 IP,两种结局。不是「路不同」,是能不能解题不同。而我前面所有的推理,都默认了浏览器能打开就等于路是通的。
可是,哪个端点?
有意思的是,并非所有端点都被拦:
1 | POST console.anthropic.com/v1/oauth/token → 400 (正常的参数错误) |
质询只下发给页面路由,/v1/oauth/* 是放行的。那 CLI 的 403 到底撞在哪个请求上?
不猜。原生安装是个单文件可执行程序,直接挖:
1 | strings ~/.local/share/claude/versions/<ver> \ |
1 | https://api.anthropic.com/api/oauth/claude_cli/roles |
platform.claude.com。既不是 console,也不是 claude.ai——难怪我前面测的两个 token 端点都是好的,我压根没在测它真正请求的那个域名。
验证,一击命中:
1 | POST platform.claude.com/v1/oauth/token → 403 |
换节点为什么没用
切到另一个国家的节点,还是 403。查出口归属才发现,两个节点分属不同国家,出口却落在同一家 IP 供应商手里。机场的主力线路共用一个被风控标记的池子,换国家是没有意义的,得换供应商。
手工试遍所有节点显然不现实。看内核启动参数:
1 | ps -o command -p $(pgrep verge-mihomo) |
有 Unix socket 控制接口。那就不用手了——切换、打端点、记状态码,写成一个循环:
1 | SOCK=/tmp/verge/verge-mihomo.sock |
结果干净得不像话:
- 403 —— 日本、美国、新加坡、台湾、香港,全部主力线路,一个不剩
- 400 —— 土耳其、埃及、阿联酋、尼日利亚、印度、南非、巴西、阿根廷
热门地区的 IP 被薅得太狠,风控画像早就花了;冷门地区反而干净。Cloudflare 的质询有概率成分,交替跑了三轮,结果稳定复现。
切到干净节点,token 端点返回的是 Anthropic 真实的 API 错误——带 request_id 的那种。放行了。
只分流一个域名
再测一遍登录链路上的全部端点:在快速节点上,只有 platform.claude.com 被拦,其余一律正常。
那就没必要把所有流量绕到地球另一端。只分流这一个域名:
| 域名 | 走哪 | 为什么 |
|---|---|---|
platform.claude.com | 干净节点组 | 只有它被拦 |
api.anthropic.com | 主力节点 | 推理流量,延迟敏感 |
claude.ai / claude.com | 主力节点 | 浏览器自己能解质询 |
组用 url-test + include-all-proxies + 关键词过滤,自动收纳那批干净节点,按延迟挑最快的,挂了自动切——订阅更新换了节点名也不容易失效:
1 | - name: Claude-OAuth |
1 | - DOMAIN,platform.claude.com,Claude-OAuth |
落盘前先用内核离线校验,别把配置写坏:
1 | verge-mihomo -t -d <config-dir> -f test-config.yaml |
- Title: 一行响应头
- Author: Nieo
- Created at : 2026-09-01 13:20:00
- Updated at : 2026-09-18 17:47:06
- Link: https://gme-hong.github.io/2026/09/01/Claude-Code-Login-Cloudflare/
- License: This work is licensed under CC BY-NC-SA 4.0.