{"record":{"id":"a913effc12bebdd3","repo":"gitbutlerapp/gitbutler","slug":"tauri-event-emission-doesn-t-fail-in-practice","errorCode":null,"errorMessage":"tauri event emission doesn't fail in practice","messagePattern":"tauri event emission doesn't fail in practice","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/gitbutler-tauri/src/main.rs","lineNumber":151,"sourceCode":"                inherit_interactive_login_shell_environment_if_not_launched_from_terminal();\n                migrate_projects().ok();\n\n                tracing::info!(\n                    \"system git executable for fetch/push: {git:?}\",\n                    git = gix::path::env::exe_invocation(),\n                );\n                if cfg!(windows) {\n                    tracing::info!(\"system git bash: {bash:?}\", bash = gix::path::env::shell());\n                } else {\n                    tracing::info!(\"SHELL env: {var:?}\", var = std::env::var_os(\"SHELL\"));\n                }\n\n                but_askpass::init({\n                    let handle = app_handle.clone();\n                    move |event| {\n                        handle\n                            .emit(\"git_prompt\", event)\n                            .expect(\"tauri event emission doesn't fail in practice\")\n                    }\n                });\n\n\n                tracing::info!(version = %app_handle.package_info().version,\n                                   name = %app_handle.package_info().name, \"starting app\");\n\n                app_handle.manage(WindowState::new(app_handle.clone()));\n\n                app_settings.watch_in_background({\n                    let app_handle = app_handle.clone();\n                    move |app_settings| {\n                        gitbutler_tauri::ChangeForFrontend::from(app_settings).send(&app_handle)\n                    }\n                })?;\n\n                let archival = but_feedback::Archival {\n                    cache_dir: app_cache_dir.clone(),","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-tauri/src/main.rs#L133-L169","documentation":"Tauri's `AppHandle::emit` broadcasts an event (here \"git_prompt\", carrying a git credential prompt collected by but_askpass) to all webview listeners and returns a `Result`. It fails when the payload cannot be serialized, when the internal event manager or channel is shut down (typically during app teardown), or when no live listener target exists. The `.expect` converts that rare failure into a panic inside the askpass callback, which takes down the whole desktop app the next time git asks for credentials.","triggerScenarios":"A git operation over HTTPS without a cached credential fires the but_askpass::init callback at crates/gitbutler-tauri/src/main.rs:151, and `handle.emit(\"git_prompt\", event)` returns Err — e.g. the payload type no longer implements Serialize after a refactor, the event manager is shut down while the app is quitting, or the webview was already destroyed.","commonSituations":"App shutdown racing an in-flight credential prompt; a Tauri major-version upgrade changing emit or event-manager semantics; a payload struct edited without keeping Serialize; emitting after the last window closed.","solutions":["Replace the .expect with a logged non-fatal branch (tracing::warn) so an emit failure can never crash the app","If the frontend must answer the prompt, make the askpass request fail so git aborts instead of hanging without an answer","Guard the shutdown race: skip emitting or ignore emit errors once the app is exiting","Add a test asserting the git_prompt payload is Serialize and matches the frontend's expected shape"],"exampleFix":"// before\nbut_askpass::init({\n    let handle = app_handle.clone();\n    move |event| {\n        handle\n            .emit(\"git_prompt\", event)\n            .expect(\"tauri event emission doesn't fail in practice\")\n    }\n});\n\n// after\nbut_askpass::init({\n    let handle = app_handle.clone();\n    move |event| {\n        if let Err(err) = handle.emit(\"git_prompt\", event) {\n            tracing::warn!(\"failed to emit git_prompt event: {err}\");\n        }\n    }\n});","handlingStrategy":"try-catch","validationCode":"// Rust: confirm a listener target still exists before emitting\nif handle.webview_windows().is_empty() {\n    tracing::debug!(\"no windows; skipping git_prompt emit\");\n    return;\n}","typeGuard":null,"tryCatchPattern":"move |event| {\n    if let Err(err) = handle.emit(\"git_prompt\", event) {\n        tracing::warn!(\"git_prompt emit failed: {err}\");\n        // fail the askpass request so git does not block awaiting an answer\n    }\n}","preventionTips":["Never use .expect on Result-returning event APIs inside long-lived callbacks","Keep event payloads plain serde types covered by tests","Make the askpass flow fail gracefully during shutdown instead of panicking"],"tags":["rust","tauri","events","panic","askpass","ipc"],"backgroundTag":"tauri-event-emission-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}