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

  1. Create/register the tray in setup() before any service can emit update_part calls
  2. Replace the .expect with early return + warning log when the tray is absent
  3. Verify get_tray_id() matches the ID used at tray creation
  4. 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

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


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/7337715837a7a738. Report an issue: GitHub.