cjpais/Handy · error

failed to create copy last transcript item

Error message

failed to create copy last transcript item

What it means

Panic when MenuItem::with_id fails to create the 'copy_last_transcript' item. Same muda failure family as the other tray items: the menu subsystem cannot allocate the item — duplicated id, invalid id/label, or the tray being rebuilt while the app handle is tearing down. Immediately after this line, menu assembly also queries TranscriptionManager state, but that is unrelated to the creation failure.

Solutions

  1. Keep 'copy_last_transcript' unique within the menu build
  2. Coalesce tray rebuilds triggered in quick succession and cancel pending rebuilds at shutdown
  3. Degrade gracefully: log and omit the item — copy-last-transcript is also reachable from history

Example fix

// before
let copy_last_transcript_i = MenuItem::with_id(
    app,
    "copy_last_transcript",
    &strings.copy_last_transcript,
    true,
    None::<&str>,
)
.expect("failed to create copy last transcript item");

// after — non-fatal creation, menu omits the item on failure
let copy_last_transcript_i =
    match MenuItem::with_id(app, "copy_last_transcript", &strings.copy_last_transcript, true, None::<&str>) {
        Ok(item) => Some(item),
        Err(e) => {
            log::warn!("copy-last-transcript item omitted: {e}");
            None
        }
    };
Defensive patterns

Strategy: fallback

Try / catch

let copy_last_transcript_i =
    match MenuItem::with_id(app, "copy_last_transcript", &strings.copy_last_transcript, true, None::<&str>) {
        Ok(item) => Some(item),
        Err(e) => {
            log::warn!("copy-last-transcript tray item skipped: {e}");
            None
        }
    };

Prevention

When it happens

Trigger: Tray menu build where MenuItem::with_id(app, "copy_last_transcript", strings.copy_last_transcript, true, None) errors — id collision or rebuild racing shutdown.

Common situations: Frequent tray refreshes (theme changes, Secure Input toggling on/off forcing menu rebuilds) overlapping quit; forks renaming/duplicating the item id.

Related errors


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

Appendix: source

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

}

/// 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;
    };

    // 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)

View on GitHub (pinned to fbd4e15fa1)