cjpais/Handy · error
failed to create separator
Error message
failed to create separator
What it means
The separator closure wraps PredefinedMenuItem::separator(app) with .expect("failed to create separator"). Separators are the cheapest muda items but go through the same native menu manager; because update_tray_menu creates ~5-8 of them per rebuild, a failing backend often surfaces here first. The panic aborts whatever thread rebuilds the tray, leaving the previous menu in place or crashing the app outright.
Source
Thrown at src-tauri/src/tray.rs:242
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).
pub fn sync_tray(app: &AppHandle) {
sync_tray_with(app, |_| {});
}
fn sync_tray_with(app: &AppHandle, update: impl FnOnce(&mut TrayInner)) {
let Some(state) = app.try_state::<TrayState>() else {
return;
};
// 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.View on GitHub (pinned to fbd4e15fa1)
Solutions
- Ensure a StatusNotifierWatcher exists on the DBus session before the app starts (tray extension, waybar, plasma-systemtray)
- Replace the .expect in the separator closure with Result propagation so the caller can abort the rebuild gracefully
- Cache one separator creation failure and disable further tray rebuilds for the session instead of panicking repeatedly
- Delay the initial update_tray_menu call until the tray icon is confirmed visible
Example fix
// before
let separator = || PredefinedMenuItem::separator(app).expect("failed to create separator");
// after
let separator = || PredefinedMenuItem::separator(app).map_err(|e| {
error!("Failed to create separator: {e}");
e
});
// then propagate with ? in a update_tray_menu that returns Result<()> Defensive patterns
Strategy: fallback
Try / catch
let separator = || PredefinedMenuItem::separator(app).map_err(|e| { error!("separator creation failed: {e}"); e });
// caller: let Some(sep) = separator() else { return; }; Prevention
- Wrap repeated constructor helpers (the separator closure) so a single failure aborts the build instead of panicking at N call sites
- Feature-detect the tray backend once per session and cache the result instead of re-hitting a broken backend on every rebuild
- Log the underlying muda error text — it distinguishes registrar/DBus issues from teardown races
When it happens
Trigger: Any rebuild of the tray (state change to Recording/Transcribing/Idle, language switch, secure-input warning toggle) on a system where the native/DBus menu backend cannot register items; the closure runs multiple times per menu build, so it multiplies the chance of hitting the broken backend.
Common situations: Linux desktops without a system-tray applet or StatusNotifierWatcher; X11 sessions where the tray DBus name vanished mid-session; running in environments where the app was started before the session bus was ready.
Related errors
- failed to create quit item
- failed to create unload model item
- failed to create model submenu
- failed to create model item
- failed to create cancel item
AI-assisted analysis of cjpais/Handy@fbd4e15fa1 (2026-08-17).
Data as JSON: /api/errors/877f52a04122167b.
Report an issue: GitHub.