clash-verge-rev/clash-verge-rev · error · anyhow::Error
core readiness changed while applying system proxy
Error message
core readiness changed while applying system proxy
What it means
Thrown by CoreManager::apply_proxy_after_start after the system proxy was applied. Before applying it captured a ProxyRestoreExpectation (running mode + core readiness generation + owner-monitor generation); after `proxy_control::apply().await` returned it re-checks that expectation. If any of the three signals changed, the just-applied proxy is rolled back (clear + stop_guard) and the function bails. It exists so the app never leaves the OS pointing at a proxy whose backing core has already moved underneath it.
Source
Thrown at src-tauri/src/core/manager/lifecycle.rs:412
}
pub(crate) async fn apply_proxy_after_start(&self) -> Result<()> {
let expectation = ProxyRestoreExpectation::capture(
*self.get_running_mode(),
self.current_core_readiness_generation(),
crate::core::service::owner_monitor_generation(),
)
.ok_or_else(|| anyhow::anyhow!("cannot apply system proxy before core readiness"))?;
proxy_control::apply().await?;
if !expectation.is_valid(
self.get_running_mode().as_ref(),
self.current_core_readiness_generation(),
crate::core::service::owner_monitor_generation(),
) {
let clear_result = proxy_control::clear().await;
proxy_control::stop_guard().await;
clear_result?;
anyhow::bail!("core readiness changed while applying system proxy");
}
proxy_control::refresh_guard().await?;
if !expectation.is_valid(
self.get_running_mode().as_ref(),
self.current_core_readiness_generation(),
crate::core::service::owner_monitor_generation(),
) {
let clear_result = proxy_control::clear().await;
proxy_control::stop_guard().await;
clear_result?;
anyhow::bail!("core readiness changed while refreshing the system proxy guard");
}
Ok(())
}
/// 调用者须已持有 `lifecycle_lock`。
async fn start_core_inner(&self) -> Result<()> {
// 退出中不再启动新内核。View on GitHub (pinned to 5cad0f2799)
Solutions
- Route every restart/config path through restart_core / restart_core_during_config_update so the config_update_in_progress guard + lifecycle_lock serialize lifecycle work; do not call start/stop directly.
- Retry the operation via restart_core — the expectation is re-captured on each attempt and a stable core will pass.
- Check owner-monitor / service health logs around the failure timestamp; if owner_monitor_generation changed, the privileged service restarted and the core must be handed back to it.
- If it reproduces, capture running_mode + both generation values before and after apply to identify which of the three signals is moving.
Example fix
// before: two callers both touching lifecycle core.start_core().await?; core.apply_proxy_after_start().await?; // races with a concurrent restart // after: single serialized entry point core.restart_core().await?; // holds config_update flag + lifecycle_lock end-to-end
Defensive patterns
Strategy: retry
Validate before calling
// Before apply, confirm no other config update is in flight and the core is stable.
if !core_manager.try_start_config_update() {
return Err(anyhow::anyhow!("defer: a config update is already running"));
}
// capture an expectation and assert it is still live right before calling apply_proxy_after_start
let pre = (
*core_manager.get_running_mode(),
core_manager.current_core_readiness_generation(),
crate::core::service::owner_monitor_generation(),
);
if pre.0 == RunningMode::NotRunning || pre.1.is_none() {
return Err(anyhow::anyhow!("defer: core not ready before proxy apply"));
} Try / catch
match core_manager.apply_proxy_after_start().await {
Ok(()) => Ok(()),
Err(e) if e.to_string().contains("core readiness changed while applying system proxy") => {
// transient race against a concurrent lifecycle op; one retry via the serialized path
core_manager.restart_core().await
}
Err(e) => Err(e),
} Prevention
- Always mutate core lifecycle through restart_core / restart_core_during_config_update so the config_update_in_progress flag and lifecycle_lock serialize work.
- Never call apply_proxy_after_start, start_core, or stop_core directly from UI/IPC handlers.
- Treat core readiness generation and owner_monitor_generation as load-bearing — log them around any proxy transition.
When it happens
Trigger: Called right after a core start/restart finishes, while `lifecycle_lock` is held. The bail fires when, during the await on `proxy_control::apply()`, one of these changed: RunningMode transitioned away (e.g. core exited or was stopped), current_core_readiness_generation() bumped (core restarted under you), or crate::core::service::owner_monitor_generation() changed (the privileged service re-established ownership).
Common situations: A second lifecycle operation racing the first (another restart_core / change_core / activate that did not take the same config_update lock); the service manager dying and respawning mid-apply; the core crashing immediately after the readiness probe so its generation bumps; rapid config edits triggering overlapping apply calls.
Related errors
- core readiness changed while refreshing the system proxy gua
- cannot apply system proxy before core readiness
- core did not become ready after restart
- Invalid vless uri
- Invalid vless uri: missing port
AI-assisted analysis of clash-verge-rev/clash-verge-rev@5cad0f2799 (2026-08-12).
Data as JSON: /api/errors/6504a18f23f33185.
Report an issue: GitHub.