cjpais/Handy · error

failed to create check updates item

Error message

failed to create check updates item

What it means

Panic when MenuItem::with_id fails to create the 'check_updates' item. Its enabled state reflects settings.update_checks_enabled, but that flag does not affect creation; failure causes are the same muda family — duplicated id, invalid label/id, or a tray rebuild racing app teardown. Because strings come from i18n, an unexpected label issue (e.g. interior NUL on some backends) is theoretically possible but not observed.

Solutions

  1. Keep 'check_updates' unique within the menu build
  2. Skip tray rebuilds during shutdown
  3. Degrade gracefully (log and build the menu without the item) since update checks also surface in the settings UI

Example fix

// before
let check_updates_i = MenuItem::with_id(
    app,
    "check_updates",
    &strings.check_updates,
    settings.update_checks_enabled,
    None::<&str>,
)
.expect("failed to create check updates item");

// after — include the id and settings state in diagnostics
let check_updates_i = MenuItem::with_id(
    app,
    "check_updates",
    &strings.check_updates,
    settings.update_checks_enabled,
    None::<&str>,
)
.unwrap_or_else(|e| panic!("failed to create check updates item (id='check_updates'): {e}"));
Defensive patterns

Strategy: fallback

Try / catch

let check_updates_i =
    match MenuItem::with_id(app, "check_updates", &strings.check_updates, settings.update_checks_enabled, None::<&str>) {
        Ok(item) => Some(item),
        Err(e) => {
            log::warn!("check-updates tray item skipped: {e}");
            None // update checks remain available in the settings UI
        }
    };

Prevention

When it happens

Trigger: Tray menu build where MenuItem::with_id(app, "check_updates", strings.check_updates, settings.update_checks_enabled, None) errors — id collision or teardown race.

Common situations: Theme-change rebuilds racing quit; forks adding a second updates entry with the same id; localized label regressions from the i18n pipeline.

Related errors


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

Appendix: source

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

        (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).
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;
    };

View on GitHub (pinned to fbd4e15fa1)