libnyanpasu/clash-nyanpasu · error
tray not found
Error message
tray not found
What it means
update_part looks up the app's tray by its tray ID and panics with 'tray not found' when tray_by_id returns None. It assumes the tray icon was already created during setup; updating menu/tooltip state on a tray that was never built (or was destroyed) is an unrecoverable invariant violation in this code path.
Source
Thrown at backend/tauri/src/core/tray/mod.rs:344
Tray::update_part(app_handle)?;
Ok(())
}
#[instrument(skip(app_handle))]
pub fn update_part<R: Runtime>(app_handle: &AppHandle<R>) -> Result<()> {
let mode = crate::utils::config::get_current_clash_mode();
let core = {
*Config::verge()
.latest()
.clash_core
.as_ref()
.unwrap_or(&ClashCore::default())
};
let tray_id = get_tray_id();
tracing::debug!("updating tray part: {}", tray_id);
let tray = app_handle
.tray_by_id(tray_id.as_ref())
.expect("tray not found");
let state = app_handle.state::<TrayState<R>>();
let menu = state.menu.lock();
let _ = menu
.get("rule_mode")
.and_then(|item| item.as_check_menuitem()?.set_checked(mode == "rule").ok());
let _ = menu
.get("global_mode")
.and_then(|item| item.as_check_menuitem()?.set_checked(mode == "global").ok());
let _ = menu
.get("direct_mode")
.and_then(|item| item.as_check_menuitem()?.set_checked(mode == "direct").ok());
if core == ClashCore::ClashPremium {
let _ = menu
.get("script_mode")
.and_then(|item| item.as_check_menuitem()?.set_checked(mode == "script").ok());
}
View on GitHub (pinned to f7dbce2997)
Solutions
- Create/register the tray in setup() before any service can emit update_part calls
- Replace the .expect with early return + warning log when the tray is absent
- Verify get_tray_id() matches the ID used at tray creation
- Gate tray update handlers until a TrayState/created flag is set
Example fix
// before
let tray = app_handle
.tray_by_id(tray_id.as_ref())
.expect("tray not found");
// after
let Some(tray) = app_handle.tray_by_id(tray_id.as_ref()) else {
tracing::warn!("tray not found, skipping update_part");
return Ok(());
}; Defensive patterns
Strategy: fallback
Validate before calling
if app_handle.tray_by_id(get_tray_id()).is_none() {
tracing::warn!("tray not yet created; skipping update");
return;
} Try / catch
match app_handle.tray_by_id(tray_id.as_ref()) {
Some(tray) => { /* update */ }
None => tracing::warn!("tray not found; skipping update_part"),
} Prevention
- Create the tray in setup() before wiring update handlers
- Never assume tray exists on every platform; guard all update paths
- Keep tray IDs consistent between creation and lookup
When it happens
Trigger: Calling update_part (e.g. from proxy mode/config change handlers) before tray creation completes, after the tray was closed/destroyed, or when get_tray_id() yields an ID that doesn't match the registered tray (rebuild after core switch, plugin re-init).
Common situations: Config or clash-core change event firing during early startup before setup() created the tray; tray creation silently failed earlier (e.g. missing tray icon resources); multiple update_part calls racing with tray rebuild on Windows.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- NyanpasuClient is not available for verge patch
- failed to get tray menu window
- failed to get tray menu window after creation
- Called register() before prepare()
- listen() called before prepare()
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/7337715837a7a738.
Report an issue: GitHub.