{"record":{"id":"70c520e7a9b87b80","repo":"cjpais/Handy","slug":"failed-to-create-model-submenu","errorCode":null,"errorMessage":"failed to create model submenu","messagePattern":"failed to create model submenu","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/tray.rs","lineNumber":260,"sourceCode":"    sync_tray_with(app, |_| {});\n}\n\nfn sync_tray_with(app: &AppHandle, update: impl FnOnce(&mut TrayInner)) {\n    let Some(state) = app.try_state::<TrayState>() else {\n        return;\n    };\n\n    // Record intent and claim a sequence number in one critical section, so\n    // sequence order == the order in which state changes were requested.\n    let (seq, icon_state) = {\n        let mut inner = state.lock();\n        update(&mut inner);\n        inner.next_seq += 1;\n        (inner.next_seq, inner.icon_state)\n    };\n\n    // Tray not built yet (early secure-input monitor callbacks). The intent\n    // is kept and picked up by the first sync after the tray exists.\n    if app.try_state::<TrayIcon>().is_none() {\n        return;\n    }\n\n    let desired = compute_desired(app, icon_state);\n\n    // Decode the icon off the main thread, once per path, outside the lock.\n    let needs_icon = !state.lock().icons.contains_key(desired.icon_path);\n    let loaded_icon = if needs_icon {\n        match load_tray_icon(\n            app.path()\n                .resolve(desired.icon_path, tauri::path::BaseDirectory::Resource),\n        ) {\n            Ok(image) => Some(image),\n            Err(err) => {\n                error!(\"Failed to load tray icon '{}': {err}\", desired.icon_path);\n                None\n            }","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/cjpais/Handy/blob/fbd4e15fa14a721c66c57006ae110428b9e255b3/src-tauri/src/tray.rs#L242-L278","documentation":"The model submenu is created with Submenu::with_id(app, \"model_submenu\", ...) and its creation is unwrapped with .expect. A submenu is a nested native menu, so on top of the usual menu-manager failures it can also fail when the backend cannot attach a child menu to the (not yet attached) parent, or when the label (the active model's display name) trips platform-specific menu limits. This builder runs on every tray rebuild, i.e. every state change, model load/unload, and language switch.","triggerScenarios":"Rebuilding the tray while no DBus menu registrar is available on Linux; a model name with problematic content in a native-menu backend; creating the submenu during app teardown; very large label strings from a corrupt model metadata entry.","commonSituations":"Minimal Wayland compositors without tray support; unloading/loading a model which triggers update_tray_menu while the session tray is being restarted; development loops that rapidly toggle states.","solutions":["Verify a tray registrar is present (Linux) and retry the rebuild once after a short delay before giving up","Return Result from update_tray_menu and skip the model submenu (append a disabled placeholder item) when Submenu::with_id fails, instead of panicking","Sanitize/truncate submenu_label (the active model name) to a sane length before passing it to the native backend","Debounce update_tray_menu calls triggered by rapid state changes so partial backends are not hammered"],"exampleFix":"// before\nlet submenu = Submenu::with_id(app, \"model_submenu\", &submenu_label, true)\n    .expect(\"failed to create model submenu\");\n\n// after\nlet submenu = match Submenu::with_id(app, \"model_submenu\", &submenu_label, true) {\n    Ok(s) => s,\n    Err(e) => {\n        warn!(\"Model submenu unavailable, using plain label item: {e}\");\n        MenuItem::with_id(app, \"model_submenu\", &submenu_label, false, None::<&str>)\n            .unwrap_or_else(|_| return None) // caller keeps old menu\n    }\n};","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"match Submenu::with_id(app, \"model_submenu\", &submenu_label, true) {\n    Ok(s) => s,\n    Err(e) => { warn!(\"model submenu unavailable: {e}\"); return; }\n}","preventionTips":["Sanitize/truncate model names used as native menu labels","Debounce update_tray_menu calls triggered by rapid model load/unload or language changes","On Linux CI, set HANDY_NO_TRAY-style behavior or run under a desktop with a tray before exercising tray paths"],"tags":["tauri","tray","submenu","muda","rust","panic"],"backgroundTag":"tauri-tray-menu-creation-failed","analyzedSha":"fbd4e15fa14a721c66c57006ae110428b9e255b3","analyzedAt":"2026-08-17T10:29:55.597Z","contentChangedAt":"2026-08-17T10:29:55.597Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}