cjpais/Handy · error
failed to create unload model item
Error message
failed to create unload model item
What it means
The unload-model MenuItem is created with enabled = model_loaded (queried from TranscriptionManager). The enabled flag only affects grey-out state, not creation success, so this .expect panics under the same general conditions as the other items: the native menu backend (muda via Tauri) refusing to register the item — overwhelmingly a Linux tray/DBusMenu availability problem or a teardown race.
Solutions
- Run the app inside a desktop session with a functional system tray when exercising tray code paths
- Make update_tray_menu fallible: on item-creation error, log and abort the rebuild keeping the previous menu
- Feature-test tray availability once at startup and skip rebuilds (hide tray, rely on --no-tray mode) when unavailable
- Avoid triggering update_tray_menu from model unload paths during shutdown
Example fix
// before
let unload_model_i = MenuItem::with_id(
app, "unload_model", &strings.unload_model, model_loaded, None::<&str>,
)
.expect("failed to create unload model item");
// after
let unload_model_i = MenuItem::with_id(
app, "unload_model", &strings.unload_model, model_loaded, None::<&str>,
)
.unwrap_or_else(|e| {
warn!("Failed to create unload model item: {e}");
return; // caller aborts rebuild, old tray stays
}); Defensive patterns
Strategy: fallback
Try / catch
let Ok(unload_model_i) = MenuItem::with_id(
app, "unload_model", &strings.unload_model, model_loaded, None::<&str>,
) else { error!("unload item failed; aborting rebuild"); return; }; Prevention
- Remember enabled=false items still require successful creation — the model_loaded flag is not a guard
- Route every menu constructor through a common fallible helper so no call site can .expect
- Test tray rebuilds on all three target OSes, especially Linux with and without a tray applet
When it happens
Trigger: Any update_tray_menu rebuild on a system lacking a StatusNotifierWatcher; a rebuild triggered by model load/unload completing while the tray backend is gone; invoking the builder after the app handle's event loop has dropped.
Common situations: GNOME without an AppIndicator extension; sway/niri/Hyprland without a bar providing tray; testing on Wayland sessions where Tauri's tray support is limited.
Related errors
- failed to create quit item
- failed to create separator
- failed to create cancel item
- failed to create menu
- failed to create model item
AI-assisted analysis of cjpais/Handy@fbd4e15fa1 (2026-08-17).
Data as JSON: /api/errors/a0b5e01906d6a084.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/tray.rs:281
}
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();
if let Some(image) = loaded_icon {
inner.icons.insert(desired.icon_path, image);
}
if seq < inner.desired_seq {
// A request triggered after this one already stored its snapshot
// (and scheduled an apply). Ours is stale; drop it.
trace!(
"tray sync: request {seq} superseded by {}",
inner.desired_seq
);
return;
}
inner.desired = Some(desired);
inner.desired_seq = seq;View on GitHub (pinned to fbd4e15fa1)