cjpais/Handy · error
failed to create settings item
Error message
failed to create settings item
What it means
Panic when MenuItem::with_id fails to create the 'settings' item, which unlike the version item registers a keyboard accelerator ('Cmd+,' on macOS, 'Ctrl+,' elsewhere). Accelerator registration is the extra failure mode here: an accelerator string that muda cannot parse or that the platform menu layer rejects makes item creation fail, in addition to the generic duplicated-id/teardown causes.
Solutions
- Verify the accelerator is valid for the platform (keep the cfg(target_os = "macos") split for Cmd/Ctrl)
- Ensure 'settings' is unique within the menu build
- Retry creation with None as the accelerator (menu still works; keyboard shortcut lost) before giving up
- Include the accelerator in the panic message for fast diagnosis
Example fix
// before
let settings_i = MenuItem::with_id(
app,
"settings",
&strings.settings,
true,
settings_accelerator,
)
.expect("failed to create settings item");
// after — fall back to no accelerator rather than aborting
let settings_i = MenuItem::with_id(app, "settings", &strings.settings, true, settings_accelerator)
.or_else(|_| {
log::warn!("settings item created without accelerator");
MenuItem::with_id(app, "settings", &strings.settings, true, None::<&str>)
})
.expect("failed to create settings item"); Defensive patterns
Strategy: fallback
Validate before calling
// Validate the accelerator string per platform before creating the item
#[cfg(target_os = "macos")]
const SETTINGS_ACCEL: Option<&str> = Some("Cmd+,");
#[cfg(not(target_os = "macos"))]
const SETTINGS_ACCEL: Option<&str> = Some("Ctrl+,");
// keep accelerators compile-time constants per platform instead of runtime-computed strings Try / catch
let settings_i = MenuItem::with_id(app, "settings", &strings.settings, true, settings_accelerator)
.or_else(|_| {
log::warn!("settings item lost its accelerator; recreating without");
MenuItem::with_id(app, "settings", &strings.settings, true, None::<&str>)
}); Prevention
- Keep accelerator strings behind cfg(target_os) constants so a platform-invalid accelerator cannot leak across builds
- Retry item creation without the accelerator before failing the whole tray
- Include the accelerator value in diagnostics when creation fails
When it happens
Trigger: Tray menu build where MenuItem::with_id(app, "settings", strings.settings, true, Some(accelerator)) errors — unparseable/unsupported accelerator for the platform, id collision, or rebuild racing shutdown.
Common situations: Forks changing the accelerator string to one the target platform's menu backend rejects; cross-platform builds where a platform-specific accelerator leaks to the wrong cfg branch; id reuse after menu restructuring.
Related errors
- failed to create check updates item
- failed to create copy last transcript item
- failed to create version item
- failed to create cancel item
- failed to create menu
AI-assisted analysis of cjpais/Handy@fbd4e15fa1 (2026-08-17).
Data as JSON: /api/errors/07a33fcb0a32c458.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/tray.rs:222
(AppTheme::Dark, TrayIconState::Recording) => "resources/tray_recording.png",
(AppTheme::Dark, TrayIconState::Transcribing) => "resources/tray_transcribing.png",
// Light theme uses dark icons
(AppTheme::Light, TrayIconState::Idle) => "resources/tray_idle_dark.png",
(AppTheme::Light, TrayIconState::Recording) => "resources/tray_recording_dark.png",
(AppTheme::Light, TrayIconState::Transcribing) => "resources/tray_transcribing_dark.png",
// Colored theme uses pink icons (for Linux)
(AppTheme::Colored, TrayIconState::Idle) => "resources/handy.png",
(AppTheme::Colored, TrayIconState::Recording) => "resources/recording.png",
(AppTheme::Colored, TrayIconState::Transcribing) => "resources/transcribing.png",
}
}
/// Sets the recording state shown by the tray (icon + Cancel/model menu).
pub fn set_tray_state(app: &AppHandle, state: TrayIconState) {
sync_tray_with(app, |inner| inner.icon_state = state);
}
/// Re-syncs the tray after something other than the recording state changed
/// (theme, Secure Input warning). The recording state itself is preserved.
pub fn refresh_tray_icon(app: &AppHandle) {
sync_tray(app);
}
/// Re-syncs the tray after something the menu depends on changed (model
/// list/selection/loaded state, language, settings).
pub fn update_tray_menu(app: &AppHandle) {
sync_tray(app);
}
/// Records the current desired tray state and schedules one apply on the main
/// thread (or lets an already-pending apply pick it up). Never blocks on the
/// main thread.
///
/// The snapshot (settings, model list, loaded state) is computed on the
/// *calling* thread on purpose: the main-thread applier must not take manager
/// locks that a worker may hold across slow work (see #1716).View on GitHub (pinned to fbd4e15fa1)