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
- Confirm the panic reproduces for any number of models — if it only happens with many items, audit get_available_models for duplicate ids
- Deduplicate/filter model entries by id before building the submenu: downloaded.dedup_by(|a, b| a.id == b.id)
- Propagate the error instead of .expect: skip the failing item and log, so one bad model does not kill the tray
- 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
- Validate ModelManager output: ids must be unique and non-empty before they become menu ids
- Per-item failures should skip the item, not panic the whole tray rebuild
- Keep item id formats stable ('model_select:{id}') because the event handler routes on them
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
- failed to create cancel item
- failed to create menu
- failed to create model submenu
- failed to create quit item
- failed to create separator
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)