cjpais/Handy · error

failed to create model item

Error message

failed to create model item

What it means

Inside the model submenu loop, one CheckMenuItem::with_id is created per downloaded model with the id format "model_select:{model.id}". The .expect panics if the native backend rejects any single item. Beyond the shared menu-manager failures, this per-item construction is the one most sensitive to item-id hygiene: duplicate model ids (two ModelManager entries sharing an id) produce duplicate menu ids in the same submenu, which breaks event routing and can error in some muda backends.

Solutions

  1. Confirm the panic reproduces for any number of models — if it only happens with many items, audit get_available_models for duplicate ids
  2. Deduplicate/filter model entries by id before building the submenu: downloaded.dedup_by(|a, b| a.id == b.id)
  3. Propagate the error instead of .expect: skip the failing item and log, so one bad model does not kill the tray
  4. Ensure a system tray provider is running on Linux when testing

Example fix

// before
let item = CheckMenuItem::with_id(app, &item_id, &model.name, true, is_active, None::<&str>)
    .expect("failed to create model item");
let _ = submenu.append(&item);

// after
if let Ok(item) = CheckMenuItem::with_id(app, &item_id, &model.name, true, is_active, None::<&str>) {
    let _ = submenu.append(&item);
} else {
    warn!("Skipping model item for {} ({item_id})", model.name);
}
Defensive patterns

Strategy: fallback

Validate before calling

// Deduplicate model ids before building submenu items
let mut seen = std::collections::HashSet::new();
downloaded.retain(|m| seen.insert(m.id.clone()));

Try / catch

if let Ok(item) = CheckMenuItem::with_id(app, &item_id, &model.name, true, is_active, None::<&str>) {
    let _ = submenu.append(&item);
} else {
    warn!("skipping model item {item_id}");
}

Prevention

When it happens

Trigger: Having several models downloaded (each gets an item) on a Linux system without a working DBusMenu registrar; a corrupt/duplicated models list from ModelManager::get_available_models where two entries share an id, producing identical "model_select:{id}" ids in one submenu; rebuild during shutdown.

Common situations: Power users with many downloaded Whisper variants; model metadata files duplicated during an interrupted download; headless or tray-less test environments.

Related errors


AI-assisted analysis of cjpais/Handy@fbd4e15fa1 (2026-08-17). Data as JSON: /api/errors/5d5895e7f015fae3. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/tray.rs:267

    // Record intent and claim a sequence number in one critical section, so
    // sequence order == the order in which state changes were requested.
    let (seq, icon_state) = {
        let mut inner = state.lock();
        update(&mut inner);
        inner.next_seq += 1;
        (inner.next_seq, inner.icon_state)
    };

    // Tray not built yet (early secure-input monitor callbacks). The intent
    // is kept and picked up by the first sync after the tray exists.
    if app.try_state::<TrayIcon>().is_none() {
        return;
    }

    let desired = compute_desired(app, icon_state);

    // Decode the icon off the main thread, once per path, outside the lock.
    let needs_icon = !state.lock().icons.contains_key(desired.icon_path);
    let loaded_icon = if needs_icon {
        match load_tray_icon(
            app.path()
                .resolve(desired.icon_path, tauri::path::BaseDirectory::Resource),
        ) {
            Ok(image) => Some(image),
            Err(err) => {
                error!("Failed to load tray icon '{}': {err}", desired.icon_path);
                None
            }
        }
    } else {
        None
    };

    let schedule = {
        let mut inner = state.lock();

View on GitHub (pinned to fbd4e15fa1)