wonfen cefcb150c9 fix: core runstate review/improve (#7617)
* fix(core): reopen PAC when a start attempt never happens

`save_proxy_ports` closes the PAC endpoint before it stages a candidate,
because the endpoint resolves the Mixed Port through the draft layer and
would otherwise answer with a port nothing is listening on.

Most ways out of that function are a rejection, though: the candidate is
refused and the Core keeps serving on its old ports, having never been
stopped. PAC is re-derived only when the Running Mode changes, and that
is exactly what did not happen — so the endpoint stayed shut for the rest
of the session, and every browser on PAC silently went direct.

Give `core_starting` the counterpart it was missing. `core_start_settled`
re-derives PAC from the Running Mode rather than restoring a saved flag,
which makes it correct on all three endings — a start that succeeded, one
that was abandoned, one that failed and left the Core down — and therefore
safe to run from a guard on every path.

* fix(config): refuse overlapping draft transactions instead of losing an edit

A layer holds exactly one draft, so a second writer's `edit_draft`
overwrites the first writer's staged value in place. `DraftTransaction`
nonetheless rolled back by calling `discard` on the shared layer, which
discarded whatever was staged there — including a change another
transaction was about to commit.

The other transaction then found no draft, and `apply` on an absent draft
does nothing at all. It saved the committed value, returned Ok, and its
side effect had already happened: auto-launch registered with the OS while
the config that describes it says otherwise, and no log line either way.

"Roll back only my edit" cannot be implemented over a single slot, so
enforce the exclusivity the type already assumed. Beginning a transaction
claims every layer and refuses rather than interleaving, which is the same
shape as `try_start_config_update` and `begin_operation` elsewhere.

`patch_verge` is the caller that matters: it holds its draft across
`process_terminated_flags`, which restarts the Core and can take seconds.
The TUN reconciliation runs on every Run State transition and can now lose
that race, so it treats a busy layer as "later" rather than as a failure
worth telling the user about.

* fix(core): reconcile TUN availability when the setting itself changes

Moving the TUN availability guard from the frontend into Rust also moved
what triggers it. The old effect depended on `enable_tun_mode`; the new
reconciliation is spawned from `RealEnv::publish`, so it only runs on a
Run State transition. Those two sets are not the same, and the setting
itself falls in the gap.

Switching TUN on from the global hotkey is the case that reaches it. That
path is not gated on availability — unlike the tray item and the settings
switch, both of which read `tun_capable` — and the flags a TUN patch
raises are answered by a Core reload, which changes no Run State at all.
So on a machine with no Service and no elevation the setting stayed on:
tray icon, settings switch and `enable_tun_mode` in verge.yaml all
reported TUN as enabled while nothing carried its traffic, until some
unrelated core lifecycle transition happened to reconcile it.

Reconcile after any patch that touched the setting. The reconciliation
writes configuration itself, so it goes through the plain apply rather
than back through the patch-and-reconcile form; the absence of a cycle is
then a property of the call graph rather than of a runtime early-return.

`toggle_tun_mode` now reads the setting back instead of returning what it
asked for, so the hotkey cannot report TUN as on moments before the notice
saying it was turned off.

* fix(core): an action already under way is not a decision to put to the user

The legacy status could not distinguish a requested privileged operation
from one already running, because readers blocked until the operation
finished and so never observed the request at all. Run State pushes every
transition instead, and `service_needs_attention` treated any pending
action as a question for the user.

So an install started from the settings page or the home card raised the
migration dialog on top of it, and an uninstall raised one offering to
reinstall the Service being removed. Both offer a button that can only
fail, since the privileged-operation slot is already taken.

An operation in flight is a report of progress. The dialog the user is
already driving keeps itself open through its own loading state.

`tun_should_be_disabled` is unaffected: it already refused to act while an
operation was in flight, so the term this adds is redundant there.

* fix(core): let the snapshot decide whether the Run State has settled

`settled` read the operation slot twice: once to decide whether to wait,
then again inside the snapshot it returned. An operation claiming the slot
between those two reads was described rather than waited for.

`prepare_startup` reads this. A start racing an install would see the
requested install, flatten it to `InstallRequired`, and decide `Wait` —
leaving the core stopped where waiting for the install to finish would
have told it whether the Service came back.

Read the slot once, inside the snapshot, so there is no window rather than
a second check closing it. The rewrite that introduced this also fixed a
lost wakeup here, and that fix stands: the waiter is still registered
before the state is read.

* fix(ui): re-read the Run State once its listener is live

Replacing the poll with a push left a gap. `listen` resolves
asynchronously, so between the first `get_runtime_state` and the
subscription being registered there is a window, and a transition landing
in it is not queued anywhere — it is simply missed. The next event is
whenever the backend happens to move again, which during startup can be a
long time; the 30-second safety net was all that stood behind it, where
the old code polled every two seconds precisely through startup.

The Run State is the one thing here that arrives only by event, so it is
the one thing a missed event leaves stale. Re-read it once every listener
is live, which closes the gap without going back to polling for it.

`onSubscribed` fires even when a registration failed, because a resync is
most needed when a subscription is missing, and not at all after teardown.

* docs(config): state what makes the PAC endpoint's draft read safe

`MixedPort::desired` reads the draft layer, so the PAC endpoint can name a
port the Core has not moved to yet. That is only safe because the one path
staging a listener port closes PAC across the change. Nothing said so at
either end, and the two are in different modules.

* fix(core): ask again when staging goes silent rather than assume it failed

The Service commits the generation before it answers, so a reply that never
arrives is not evidence that nothing was written: config.yaml can already hold
the new configuration while this side concludes the staging never happened. The
Core keeps running the previous configuration, so nothing looks wrong -- until
the Core dies and the Service's watchdog restarts it in the same generation from
whatever config.yaml says, quietly making live a configuration the app reported
as failed.

Silence is the one answer that has to be resolved rather than acted on, so ask
once more before falling back. It is cheap exactly where it matters: staging
plans against the manifest it wrote, so a bundle that already landed copies
nothing and answers in milliseconds, and the Service's lifecycle lock makes a
second ask wait for a first one still in flight instead of interleaving with it.
A deadline keeps that wait off the caller, and a second silence goes where the
first one used to.
2026-07-29 03:58:47 +08:00
2026-07-20 00:24:35 +08:00
2026-07-28 15:16:01 +00:00
2026-07-27 00:44:53 -07:00
2026-03-15 07:40:11 +00:00
2022-11-12 11:37:23 +08:00
2026-07-28 15:16:01 +00:00
2026-03-15 07:40:11 +00:00
2026-07-13 11:29:25 +08:00
2021-12-22 01:12:32 +08:00
2026-07-28 15:16:01 +00:00
2026-07-28 15:16:01 +00:00
2026-07-26 01:29:44 +08:00

Clash
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

捐助Clash Verge Rev的开发

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:

License

GPL-3.0 License. See License here for details.

S
Description
No description provided
Readme GPL-3.0
527 MiB
Languages
Rust 46.4%
TypeScript 45.4%
JavaScript 5.9%
NSIS 1.4%
Shell 0.5%
Other 0.3%