开着 TUN 代理,官方 Claude 桌面版还是连不上?一次 Electron + QUIC 的真实排查
2026-09-06 更新:这篇 09-05 发出去的第一版,解法(把规则写进 Clash Verge 的全局
Merge.yaml)其实没生效,第二天 Claude 照样连不上。这次我用 mihomo 的连接表把真正的根因抓了个正着,改法也换了,并且用 API、日志、截图三样东西验证过。原来写错的段落直接改掉了,文末有一节「勘误」说明改了什么。
起因
我用官方 Claude 桌面版,一直得配一个叫 "Claude Proxy" 的小程序才能连上。流程很别扭:每次官方 Claude 自动更新之后,就连不上了,得退出 Claude、再用 Claude Proxy 重开一次,才恢复。更新一次折腾一次,烦了很久。
直接点图标打开,看到的是这一屏:
Couldn't connect to Claude Your network redirected this request to www.anthropic.com. Contact your IT administrator.
奇怪的是,我机器上明明开着 Clash Verge 的 TUN 模式——按我的理解,TUN 模式该在网络层接管所有流量,装了就该"全都通",怎么单单一个 Claude 要额外挂代理?
带着这个疑问查下去,发现坑不在 Claude,也不在 TUN 本身,而在两件事叠在一起:Electron / Chromium 默认优先走 QUIC,以及 mihomo 在"节点不支持 UDP"时的一个放行行为。
关键词(可以展开学习): QUIC / HTTP3、TUN 虚拟网卡、HTTP CONNECT 代理、Chromium 网络栈、mihomo 规则匹配、fake-ip
先搞清楚 "Claude Proxy" 到底是什么
这类"打辅助"的小程序,很多其实就是个壳。我把它反编译看了一眼——macOS 上用「脚本编辑器」导出的 applet,真正逻辑就藏在里面的 main.scpt:
osadecompile "/Applications/Claude Proxy.app/Contents/Resources/Scripts/main.scpt"
输出只有一行:
do shell script "open -a Claude --args --proxy-server=http://127.0.0.1:7897"
翻译成人话:它就是用 --proxy-server 这个参数重新启动官方 Claude,让 Claude 把流量走本地 127.0.0.1:7897(Clash 的混合端口)。--proxy-server 是 Chromium 系(Chrome、Electron 应用都算)都认的命令行开关。
为什么每次更新都要重来一遍
看懂这一行,反复失效的原因也就清楚了,两点凑一起:
这个参数只在"冷启动"时生效。
open -a Claude --args ...有个容易忽略的细节:如果 Claude 已经在跑,这条命令只会把它切到前台,不会带着新参数重启它。所以你必须先彻底退出、再重开,参数才进得去——这就是"退出重开"这个动作的由来。官方更新后会自己重启,而且是不带参数地重启。 更新装好,Claude 把自己重新拉起来,走的是它自己的默认启动方式,没有那个
--proxy-server→ 连不上。于是你又得手动"退出 + 用 Claude Proxy 重开"一次。
一句话:这套做法本身没错,只是它不抗更新。 参数是"一次性"的,而更新是"反复"的。
真正的问题:开着 TUN,为什么还需要这个参数?
先做最简单的对照实验——完全不指代理,纯靠 TUN 透明路由,用 curl 去连:
curl -o /dev/null -w "%{http_code}\n" https://api.anthropic.com/v1/models # 401,到达了(只是没带 key)
curl -o /dev/null -w "%{http_code}\n" https://claude.ai/ # 403,也到达了,不是超时
curl -s https://claude.ai/cdn-cgi/trace | grep -E "^(ip|loc)=" # ip=104.243.x.x loc=US
命令行的 TCP 请求靠 TUN 已经能正常到达,出口也是美国。那 Claude 桌面版凭什么连不上?
第一条线索:桌面版的日志
Claude 桌面版把渲染进程的日志写在 ~/Library/Logs/Claude/claude.ai-web.log,里面反复出现这一句:
Access to fetch at 'https://www.anthropic.com/app-unavailable-in-region'
(redirected from 'https://claude.ai/api/organizations/.../usage') ... blocked by CORS policy
主进程日志 main.log 则是:
[warn] Blocked redirect to disallowed URL { origin: 'https://www.anthropic.com' }
[info] Main view load failure #10 (redirect_blocked)
注意这不是超时、不是握手卡住:请求确实到了 Anthropic,Anthropic 回了一个 app-unavailable-in-region(你所在地区不可用)的跳转。换句话说——Claude 桌面版的请求,是带着大陆 IP 直连到 Anthropic 的。同一台机器,curl 是美国出口,桌面版却是大陆出口。
第二条线索:mihomo 的连接表
mihomo(Clash Verge 内核)有个 REST API,/connections 能看到每条连接匹配了哪条规则、走了哪个节点。Clash Verge 2.x 服务模式下它是个 unix socket:
SOCK=/var/run/clash-verge-service/users/$(id -u)/verge-mihomo.sock
curl -s --unix-socket $SOCK -H "Authorization: Bearer <secret>" http://x/connections \
| python3 -c '
import sys,json
for c in json.load(sys.stdin)["connections"]:
m=c["metadata"]
if "claude" in (m.get("host") or ""):
print(m["network"], m["host"], m["destinationIP"], repr(c["rule"]), c["chains"])'
结果一眼就看出问题:
tcp claude.ai "" 'DomainSuffix' ['c69s3', 'Falcon-USA', 'Claude'] ← Chrome / curl
udp claude.ai 160.79.104.10 '' ['DIRECT'] ← Claude Helper
udp claude.com 160.79.104.10 '' ['DIRECT'] ← Claude Helper
TCP 连接老老实实匹配了 DOMAIN-SUFFIX,claude.ai,Claude,走美国节点;而 Claude 桌面版(进程名 Claude Helper)对同一个域名发的 UDP/443 连接——也就是 QUIC——没匹配任何规则(rule 为空),直接 DIRECT 出去了。目标 IP 160.79.104.10 是 Anthropic 自己的地址段,真实 IP,不是 fake-ip。
所以 TUN 没有"接管不稳",包也没丢——它把 QUIC 稳稳地接住了,然后放行直连。QUIC 握手成功、Anthropic 看到大陆 IP、返回地区不可用。比"连不上"更糟:它把你的真实 IP 送到了对方门口。
为什么会放行 DIRECT
这是 mihomo 规则匹配的一个明确行为:一条 UDP 连接命中了某条规则,但这条规则指向的出站(节点/分组)不支持 UDP,mihomo 会跳过这条规则继续往下匹配。我的 Claude 分组当前节点是 JustMySocks 的 VMess/VLESS,udp: false;往下 MATCH,PROXY 指向的分组同样是这个节点,也被跳过;规则表走到底没有一条能接,就落到默认的 DIRECT——日志里对应的是 doesn't match any rule using DIRECT。
这也解释了 --proxy-server 当初为什么管用:Chromium 走显式 HTTP 代理时用的是 TCP 的 CONNECT 隧道,QUIC 穿不过普通 HTTP 代理,它就自动回退 TCP。那个参数之所以有效,本质是它顺手把 QUIC 关掉了。
想通这层,解法就浮出来了:在网络层把 QUIC 拒掉,逼 Chromium 用 TCP,跟"怎么启动、更新几次"完全无关。 这个方向 09-05 那版就对了——错在把规则写到了一个不生效的地方。
怎么确认是不是这个问题
不用猜,三步就能对上号,而且别只用 curl——curl 不走 QUIC,永远测不出这个问题:
# 1. TCP 是通的(纯 TUN、不指代理)
curl -o /dev/null -w "直连 %{http_code}\n" --max-time 8 https://api.anthropic.com/v1/models
# 2. 桌面版日志里有没有"地区不可用"跳转
grep -c "app-unavailable-in-region" ~/Library/Logs/Claude/claude.ai-web.log
# 3. mihomo 连接表里,有没有 udp + 443 + DIRECT 的条目(上一节的脚本)
第 1 条正常、第 2 条大于 0、第 3 条能看到 udp ... ['DIRECT'],就是这个坑。
解法:拒绝 QUIC,逼 Chromium 回退 TCP
先说两个我没选的方案:
- 开系统代理? Chromium 会自动读系统代理,QUIC 也就不走了。但如果你还要用公司 VPN,系统代理这一栏会打架,排除。
- 换一个支持 UDP 的节点给 Claude 分组? 能解决——UDP 有人接了,QUIC 就会被正常代理。但这等于把正确性押在"当前节点恰好支持 UDP"上,节点一换又回来。可以作为补充,不作为主方案。
真正对症的是在 mihomo 里直接拒绝 QUIC。核心就一条规则,REJECT 这个出站是支持 UDP 的,所以它不会像上面那样被跳过:
# 拒绝所有 UDP:443(QUIC),强制回退 TCP
- AND,((NETWORK,udp),(DST-PORT,443)),REJECT
写在哪:这是 09-05 那版踩的坑
我用的是 Clash Verge 2.5.4。第一版把它写进了全局 Merge(profiles/Merge.yaml)的 prepend-rules: 里,界面上重新激活配置也没报错,我就以为生效了。第二天再查:
grep -n "DST-PORT" ~/Library/Application\ Support/io.github.clash-verge-rev.clash-verge-rev/clash-verge.yaml
# 13:- AND,((NETWORK,udp),(DST-PORT,443)),REJECT ← 在第 13 行,也就是文件顶部
它出现在生成后配置的顶层,作为一个孤零零的 prepend-rules: 键留在那儿——没有被并进 rules: 列表。mihomo 不认识这个键,直接忽略。再查内核真正加载的规则表,头几条还是原来的:
curl -s --unix-socket $SOCK -H "Authorization: Bearer <secret>" http://x/rules \
| python3 -c 'import sys,json; r=json.load(sys.stdin)["rules"]; print(len(r)); [print(x["type"],x["payload"],x["proxy"]) for x in r[:5]]'
所以在这个版本的 Clash Verge 上,全局 Merge 的 prepend-rules 是不生效的(订阅右键那个"编辑合并"我没验证,不敢打包票)。稳妥的做法是直接写进配置文件自己的 rules: 段最前面——在任何"直连/代理"的域名规则之前,让它先命中:
rules:
- PROCESS-NAME,Tailscale,DIRECT # 这类进程级直连可以留在前面
- AND,((NETWORK,udp),(DST-PORT,443)),REJECT # QUIC 一律拒绝
- DOMAIN-SUFFIX,claude.ai,Claude
# ... 其余规则
改完重新激活配置,然后一定用 /rules 接口确认一下:规则总数应该 +1,前几条里能看到 AND ((Network,udp) && (DstPort,443)) -> REJECT。界面上看着"已激活"不算数。
为什么用"全局拒绝"而不是"只对 Claude 域名"? 在 fake-ip 模式下,这条 UDP 连接其实是带域名的(上面连接表里 host=claude.ai),按域名写理论上也能命中。但应用如果自己用 DoH 解析、绕开了 fake-ip,域名就没了,规则就漏。全局拒绝 UDP:443 不依赖任何域名解析,必中。代价是所有 App 都不走 QUIC 了——QUIC 永远有 TCP 兜底,功能上无损,这也是很多国内 Clash 配置的默认做法。
确实想只对 Claude 生效的话:
- AND,((NETWORK,udp),(DST-PORT,443),(DOMAIN-SUFFIX,claude.ai)),REJECT
- AND,((NETWORK,udp),(DST-PORT,443),(DOMAIN-SUFFIX,anthropic.com)),REJECT
验证
这次不靠"打开看看能不能用",三个东西都过了才算:
- 规则真的在内核里:
/rules从 1358 条变 1359 条,第 5 条就是它。 - 桌面版日志:彻底退出 Claude(
⌘Q),从 Dock 正常打开,main.log出现Loaded https://claude.ai,不再有Blocked redirect to disallowed URL。 - 连接表:
Claude Helper对claude.ai/a-cdn.anthropic.com/bridge.claudeusercontent.com的连接全部是 tcp,链路c69s3 / Falcon-USA / Claude,没有一条 DIRECT 的 UDP。
之后官方 Claude 无论怎么启动、更新多少次,都不再需要那个 --proxy-server 参数,"Claude Proxy" 可以删了。
这套思路能推广到哪儿
这个坑的本质跟 Claude 没关系,是两件事撞在一起:
- Chromium 系应用优先走 QUIC(UDP/443);
- mihomo 在"命中规则但节点不支持 UDP"时,会一路放行到 DIRECT。
第 2 条值得单独记住:它意味着凡是你的节点不支持 UDP,所有 UDP 流量都在用你的真实 IP 直连——不只是 QUIC,DNS over QUIC、游戏、语音都一样。对"地区检测"类服务(Claude、ChatGPT、Netflix 这些)来说,这就是一次实打实的 IP 泄露。
所以凡是你在国内、开着 TUN、又遇到某个 Electron / Chromium 应用"浏览器能开、这个 App 就是连不上/被判地区不可用"的,先看 mihomo 连接表里有没有 udp ... DIRECT:有,多半是同一个坑,同一条规则解决。
→ 相关:《同时装了 Tailscale 和 Clash?一次 DNS 被悄悄劫持的真实排查》——另一个"TUN 装了却没全接管"的经典场景。
延伸:QUIC 是什么,为什么它值得单独拎出来说
QUIC 一句话概括:把 TCP + TLS 那一整套,搬到 UDP 上重做一遍。 传统 HTTPS 是 TCP 负责可靠传输、再叠一层 TLS 加密,两次握手叠加、建连慢;而且 TCP 只要丢一个包,后面的数据全得排队等重传(队头阻塞 HoL)。QUIC 直接跑在 UDP 上,自己实现可靠性和多路复用,顺手把这些痛点都改了:
- 建连快:TLS 1.3 内建,握手和加密合并,通常 1-RTT,老连接恢复甚至 0-RTT(几乎瞬连)。
- 没有队头阻塞:多个流相互独立,一个流丢包不拖累其他流——一个页面并发几十个请求时特别明显。
- 连接迁移:连接标识不绑 IP/端口,Wi-Fi 切 4G 不断线——移动端的杀手锏。
- HTTP/3 就是"跑在 QUIC 上的 HTTP"。
为什么 Chromium / Electron 优先用它? 因为 QUIC 本来就是 Google 搞出来的(早期叫 gQUIC,后来标准化成 IETF QUIC / HTTP/3),Chromium 是它的头号试验田,默认开启、优先尝试:对方(比如 Cloudflare、Google)支持 HTTP/3,Chromium 就先发 QUIC,只有走不通才回退 TCP。平时这套"优先 QUIC"在直连下更快,但一旦撞上"UDP 没人接、被放行直连"的代理环境,它就从"优化"变成"泄露点"——正是本文的坑。
再认清 Electron:Electron = Chromium 渲染 + Node.js,本质是"把一个网页套壳成桌面 App",所以它天然继承 Chromium 的网络栈,也就继承了"优先 QUIC"这个行为。当前一大票你天天在用的桌面软件都是 Electron 做的:
- VS Code、Slack、Discord、Notion、Obsidian、Figma 桌面版、Postman、1Password、Signal / WhatsApp 桌面版、Todoist、Logseq……以及本文的官方 Claude 桌面版。
- 少数看着像、但严格说不是纯 Electron:新版微软 Teams 转到了 WebView2,Spotify 用自家的 CEF——但它们同样是 Chromium 内核,遇到 QUIC 的行为一致。
碰到这些应用里任何一个"浏览器能开、桌面版连不上/巨慢",都能套本文这套排查。
QUIC 还广泛用在哪? 它早不是实验技术了,你每天都在用:
- 网站:Google 全家桶、YouTube(视频优先走 QUIC)、以及所有走 Cloudflare / Fastly 的站点,默认都开 HTTP/3。
- 手机 App:Facebook / Instagram / WhatsApp 等 Meta 系大量用 QUIC,各家 CDN 的移动端也普遍启用。
- 系统层:DNS over QUIC(DoQ)、微软的 SMB over QUIC(不用 VPN 直接访问文件共享)都在铺开。
- 翻墙圈:Hysteria2、TUIC 这些新协议本身就建在 QUIC 上——所以本文那条"拒绝 UDP:443"的规则要小心别误伤自己的节点(见下方常见问题)。
一句话:QUIC 是大势,"优先 QUIC" 让绝大多数直连场景更快;本文的坑不是 QUIC 不好,而是它跟"节点不支持 UDP 的代理环境"这个特定组合八字不合。
常见问题
为什么 curl 测不出来? curl 默认不走 HTTP/3,它发的全是 TCP,TCP 在规则表里有节点接。要复现只能靠 Chromium 系应用本身,或者直接看 mihomo 连接表。
全局关掉 QUIC,会不会影响别的软件? 基本不会。QUIC 在设计上就要求有 TCP/HTTP2 兜底,关了只是回退,功能不丢。唯一可能有感的是个别重度用 QUIC 的视频/大流量场景速度略有变化,介意的话改成上面的"按域名"写法即可。
这条规则会不会把我自己代理节点的 UDP 也 ban 了? 一般不会。mihomo 连接代理服务器本身的那条外连不走规则表,所以普通节点不受影响。但如果你的节点用的是 Hysteria2 / TUIC 这类基于 UDP/QUIC 的协议,且恰好也在 443,加规则后节点连不上,就把规则收窄成"按域名",别用全局版。
我的节点本来就支持 UDP,还需要这条规则吗? 不加也能用:QUIC 会被正常代理出去。但节点一换(比如 fallback 分组切到另一个不支持 UDP 的),问题就悄悄回来,而且症状是"被判地区不可用"而不是"连不上",很难第一时间想到是代理。这条规则便宜、确定,建议留着。
为什么不干脆让 Claude 一直带参数启动? 因为"带参数启动"这件事你控制不了——官方更新会用它自己的默认方式把 App 重新拉起来,绕过你所有的启动包装。把问题下沉到网络层,才不受它启动方式的摆布。
Windows / 其他 Electron 应用也一样吗? 原理完全一致:QUIC = UDP:443,节点不支持 UDP 就被放行直连。VS Code、Slack、Discord 这些也都是 Chromium 内核,遇到同样症状用同样思路。具体规则语法看你用的代理内核,mihomo 系的写法就是上面这条。
勘误(2026-09-06)
相对 09-05 的第一版,这几处改掉了:
- 根因描述:第一版说"TUN 对 QUIC 接管不稳,包被丢、Chromium 卡在握手上",是猜的。实际是 TUN 接住了 QUIC,但 mihomo 因为节点不支持 UDP 把它放行直连,Anthropic 收到大陆 IP 后返回地区不可用。症状是"redirected to www.anthropic.com",不是超时。
- 改法位置:第一版让你写进 Clash Verge 全局
Merge.yaml的prepend-rules。在 2.5.4 上这不会并进规则表,是个不生效的死键。现在改为直接写进配置的rules:段,并用/rules接口确认。 - "从此正常开 Claude 就能连":第一版这句话说早了,当时没有用 API 验证规则是否真的加载。这版的三步验证是真跑过的。