* fix(core): put the user's node choices back when the core starts mihomo restores the node picked in each group from the `cache.db` it keeps in the directory it was started against. The app relied on that entirely, and it does not hold: `profile.store-selected` comes from a merge template a user is free to replace, and a service older than the durable runtime generation starts every core in a directory nothing has ever run in. Both end the same way — every `select` group falls back to the first entry in its `proxies:` list. The app already knows better. It records each selection in the profile, and `activate_selected_nodes` already reconciles that record against the running core — it was only ever called when switching profiles, never when a core came up. Call it from the three places a core process becomes ready. Repeat calls supersede each other, so no coordination is needed between the start paths. That makes the profile authoritative, which turns any selection it does not know about into one the next start silently undoes. The chain proxy moved a group without recording it, so recording is now one shared hook that both it and the proxy list go through. Restoration is spawned, not awaited: it polls the core until its groups are readable, and a start must not wait for that. * fix(tray): record the node the tray switched to Restoring the profile's selections when a core starts made the profile authoritative, which turns any selection it never learned about into one the next start silently undoes. The proxy list and the chain proxy both record; the tray did not. So switching node from the tray survived only until the next restart, and then reverted to whatever the profile last knew — a regression the restoration itself introduced, and only for the tray. `record_selected_node` is the backend counterpart of the frontend's `useRecordSelection`. Failing to record does not fail the switch: the core is already on the new node by then, and reporting the switch as failed would be wrong. It is logged instead. * fix(core): restoring selections must not prune what the core has not loaded Putting the profile's selections back when a core starts reused the reconciliation written for profile switches, and that one also *repairs*: a record whose group is absent from two snapshots a second apart is dropped, and the reduced list is written to profiles.yaml. After a profile switch the configuration is loaded, so absence is evidence. After a core start it is not — a provider-backed group can be empty or missing for seconds — and a record pruned on that evidence is gone for good, because the provider finishing later cannot bring it back. Restoring now keeps the records and only applies what it can match; pruning stays with the caller entitled to conclude it. The restore is also asynchronous: it reads the profile, polls the core until its groups are readable, then puts its selections. A choice the user makes inside that window is newer than what it captured, so letting it finish pushed the core back to the older node while the profile already held the newer one. Recording a selection now supersedes any activation in flight — from the tray, and from the UI and chain proxy through `patch_profile`. * fix(core): activate the selections of the profile that is actually running The activation worker read the profiles draft. A profile switch stages its target there before validating it, so a switch that fails — leaving the previous profile running — could still have handed an activation the rejected profile's selections, which it would then apply to whichever same-named groups the running profile has. Reading the committed state removes the window. The switch that succeeds commits before it activates, so nothing changes for that caller; what changes is that a restore running at core start can no longer observe a switch that never happened. * fix(core): stop a restoration from undoing a choice made while it runs Two gaps in making the profile authoritative, both found by adversarial review of the commits that introduced it. A restoration reads the profile's selections once, then polls the core until its groups can be read — seconds, on a cold start. A selection made inside that window is newer than what the restoration is carrying, and nothing told the restoration so: it went on to apply the older choice over the user's. The UI accepted the new node and traffic snapped back to the old one, with the profile and the running core then disagreeing. Recording a selection now abandons any restoration still in flight, through the same generation counter every other superseding caller uses. The other gap is the home card in global mode, which passed `skipConfigSave` and so recorded nothing. That was harmless while nothing re-applied the profile; now it means the next start undoes the choice. It was the only caller that ever set the flag, so the flag is gone rather than flipped — a parameter whose one use was a bug is not worth keeping. * fix(core): wait for the selections that can be put back, keep trying for the rest Two gaps in restoring the profile's selections when a core starts, both found by adversarial review. Restoring re-checked once, a second later, and stopped. That is enough after a profile switch, which happens with the configuration already loaded. A core start is the opposite case: a provider-backed group is present but empty until its provider finishes loading, and a `select` can fail while the core is still settling. Neither produces an activation, so the restore reported success having applied nothing and the group stayed on the first entry of its `proxies:` list — the outcome restoring exists to prevent. It now keeps looking, on a deadline, at what the core is actually on rather than at what it managed to send. Restoring was also spawned, and the caller enables the system proxy the moment the start returns — so traffic could be pointed at a core still on the wrong node. The first pass is now awaited, bounded, and the rest keeps settling in the background: a core that will not answer delays a start rather than blocking it, because being proxied through the wrong node beats not being proxied at all. TUN is deliberately not covered, and cannot be by anything here: it is configured in the core's own file, so it carries traffic from the moment the process starts. What puts a TUN user on the right node from the first packet is the core's own `cache.db`, which is why the service keeps an owner's runtime generation across restarts. `supersede_selected_activation` and `cancel_selected_node_activation` were the same function under two names, added independently for the same reason, and `patch_profile` called both. One survives, called once, after the profile is written — the only placement that needs to exist, since an activation that read the older profile is cancelled by it and one starting afterwards reads the newer one. * fix(core): record a selection by naming it, not by resending the list Recording a node selection sent the whole selection list, built from the profile the frontend had rendered. Two selections made before that list refreshed were therefore built from the same stale snapshot, and the later write dropped the earlier group's choice. Since a core start re-applies whatever the profile holds, the dropped one came back on the next restart — the profile and the running core disagreeing, in the direction the user would least expect. The tray already avoided this, because it went through `record_selected_node`, which merges one group into the profile inside `with_data_modify`. Exposing that as a command and pointing the frontend at it leaves one way to record a selection instead of two, and removes the list from the wire: what a caller knows is the group and the node, and that is now all it says. `useRecordSelection` no longer reads the profile at all, which also makes it stable across renders rather than changing identity whenever the profile does. * test(core): make every way of declining to stage answer for itself `CoreRestarted` was added to `StageRejection` upstream and reached production without a test. It inherited the right verdict — every `RestartRequired` is matched together — but nothing said so, and nothing would have noticed if it had inherited the wrong one. The list of rejections now goes through a wrapper whose `match` is exhaustive, so the next variant added upstream fails to compile here. That is the part worth having: it does not prove the list is complete, but it does mean nobody can add a way to decline without being sent to this test to say what it should lead to. * build(deps): pin the service protocol to v2.6.1 The pin tracked `feat/stage-runtime` because the protocol change it carried had no tag yet. It has one now, and a branch pin makes the build irreproducible: an upstream force-push silently changes what this compiles against. v2.6.1 carries the runtime generation an owner keeps across restarts, which is what lets the core hold on to the `cache.db` its node selections and fake-ip leases live in. The protocol itself is untouched — epoch, revision and both compatibility floors are byte-identical to v2.6.0 — so this is a fix release in both directions: an older service still works with this app, and this app still works with an older service, only without the durable generation. The lockfile change is the two lines that name it. Reaching for `cargo update -p` instead re-resolved a subtree and quietly downgraded `windows-sys`, `windows-core` and `getrandom` along the way.
Continuation of Clash Verge
A Clash Meta GUI based on Tauri.
Languages: 简体中文 · English · Español · Русский · 日本語 · 한국어 · فارسی
Preview
| Dark | Light |
|---|---|
![]() |
![]() |
Install
请到发布页面下载对应的安装包:Release page
Go to the Release page to download the corresponding installation package
Supports Windows (x64/x86), Linux (x64/arm64) and macOS 11+ (intel/apple).
我应当怎样选择发行版
| 版本 | 特征 | 链接 |
|---|---|---|
| Stable | 正式版,高可靠性,适合日常使用。 | Release |
| Alpha(废弃) | 测试发布流程。 | Alpha |
| AutoBuild | 滚动更新版,适合测试反馈,可能存在缺陷。 | AutoBuild |
安装说明和常见问题,请到 文档页 查看
TG 频道: @clash_verge_rev
Promotion
✈️ 狗狗加速 —— 技术流机场 Doggygo VPN
🚀 高性能海外技术流机场,支持免费试用与优惠套餐,全面解锁流媒体及 AI 服务,全球首家采用 QUIC 协议。
🎁 使用 Clash Verge 专属邀请链接 注册即送 3 天免费试用,每日 1GB 流量:👉 点此注册
核心优势:
- 📱 自研 iOS 客户端(业内"唯一")技术经得起考验,极大持续研发投入
- 🧑💻 12小时真人客服(顺带解决 Clash Verge 使用问题)
- 💰 优惠套餐每月仅需 21 元,160G 流量,年付 8 折
- 🌍 海外团队,无跑路风险,高达 50% 返佣
- ⚙️ 集群负载均衡设计,负载监控和随时扩容,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
- ⚡ 全球首家Quic 协议机场,现已上线更快的 Quic 类协议(Clash Verge 客户端最佳搭配)
- 🎬 解锁流媒体及 主流 AI
🌐 官网:👉 https://狗狗加速.com
🤖 GPTKefu —— 与 Crisp 深度整合的 AI 智能客服平台
- 🧠 深度理解完整对话上下文 + 图片识别,自动给出专业、精准的回复,告别机械式客服。
- ♾️ 不限回答数量,无额度焦虑,区别于其他按条计费的 AI 客服产品。
- 💬 售前咨询、售后服务、复杂问题解答,全场景轻松覆盖,真实用户案例已验证效果。
- ⚡ 3 分钟极速接入,零门槛上手,即刻提升客服效率与客户满意度。
- 🎁 高级套餐免费试用 14 天,先体验后付费:👉 立即试用
- 📢 智能客服TG 频道:@crisp_ai
Features
- 基于性能强劲的 Rust 和 Tauri 2 框架
- 内置Clash.Meta(mihomo)内核,并支持切换
Alpha版本内核。 - 简洁美观的用户界面,支持自定义主题颜色、代理组/托盘图标以及
CSS Injection。 - 配置文件管理和增强(Merge 和 Script),配置文件语法提示。
- 系统代理和守卫、
TUN(虚拟网卡)模式。 - 可视化节点和规则编辑
- WebDav 配置备份和同步
FAQ
Refer to Doc FAQ Page
Donation
Development
See CONTRIBUTING.md for more details.
To run the development server, execute the following commands after all prerequisites for Tauri are installed:
pnpm i
pnpm run prebuild
pnpm dev
pnpm dev preserves the Development Channel's installed service state: an
existing service is used, while a previously uninstalled service remains
uninstalled and the app starts in Sidecar mode. Use pnpm dev:service to
explicitly install or update the isolated development service before launch,
or pnpm dev:sidecar to force the unprivileged Sidecar workflow.
Contributions
Issue and PR welcome!
Acknowledgement
Clash Verge rev was based on or inspired by these projects and so on:
- zzzgydi/clash-verge: A Clash GUI based on tauri. Supports Windows, macOS and Linux.
- tauri-apps/tauri: Build smaller, faster, and more secure desktop applications with a web frontend.
- Dreamacro/clash: A rule-based tunnel in Go.
- MetaCubeX/mihomo: A rule-based tunnel in Go.
- Fndroid/clash_for_windows_pkg: A Windows/macOS GUI based on Clash.
- vitejs/vite: Next generation frontend tooling. It's fast!
License
GPL-3.0 License. See License here for details.

