libnyanpasu/clash-nyanpasu · error

Failed to get module handle: {err}

Error message

Failed to get module handle: {err}

What it means

On Windows, the shutdown-hook thread needs the module handle of the running executable (GetModuleHandleW(null)) to register a window class for the hidden message-only window that receives WM_QUERYENDSESSION/console signals. If GetModuleHandleW returns null, the code converts the last OS error via Error::from_win32() and bails with this message, so hook setup fails before the window is created.

Source

Thrown at backend/tauri/src/shutdown_hook.rs:128

fn setup_shutdown_hook_inner(
    f: impl Fn() + Send + Sync + 'static,
    initd_tx: oneshot::Sender<()>,
) -> anyhow::Result<()> {
    let class_name = w!("TAURI_SHUTDOWN_HOOK");

    let (tx, rx) = std::sync::mpsc::channel::<()>();
    std::thread::spawn(move || {
        while let Ok(()) = rx.recv() {
            f();
        }
    });

    SHUTDOWN_HOOK_INSTANCE.set(tx).unwrap();

    let h_instance = unsafe { GetModuleHandleW(std::ptr::null()) };
    if h_instance.is_null() {
        let err = Error::from_win32();
        anyhow::bail!("Failed to get module handle: {err}");
    }

    let mut window_class_ex = unsafe { std::mem::zeroed::<WNDCLASSEXW>() };
    window_class_ex.cbSize = std::mem::size_of::<WNDCLASSEXW>() as u32;
    window_class_ex.lpszClassName = class_name.as_ptr();
    window_class_ex.lpfnWndProc = Some(callback);
    window_class_ex.hInstance = h_instance;

    unsafe {
        if RegisterClassExW(&window_class_ex) == 0 {
            let err = Error::from_win32();
            anyhow::bail!("Failed to register window class: {err}");
        }
    }

    let window_name = w!("TAURI_SHUTDOWN_HOOK_WINDOW");
    let hidden_window = unsafe {
        CreateWindowExW(

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Inspect the Win32 error embedded in the message (err) and address its cause (permissions, host environment, resource limits)
  2. Run the app as a normal user-launched process rather than inside a restrictive service host if signal handling via window messages is required
  3. Add a fallback shutdown path (e.g. console ctrl handler via SetConsoleCtrlHandler) if GetModuleHandleW fails, instead of hard-failing setup
  4. Log the error and continue app startup, treating graceful-signal shutdown as degraded (best-effort) rather than fatal

Example fix

// before: hard failure on missing module handle
if h_instance.is_null() {
    let err = Error::from_win32();
    anyhow::bail!("Failed to get module handle: {err}");
}
// after: degrade gracefully, keep app running without signal window
if h_instance.is_null() {
    let err = Error::from_win32();
    log::warn!("shutdown signal window unavailable: {err}");
    return Ok(());
}
Defensive patterns

Strategy: try-catch

Try / catch

if let Err(e) = setup_shutdown_hook(on_shutdown) {
    if e.to_string().starts_with("Failed to get module handle") {
        log::warn!("graceful shutdown signals unavailable: {e}; continuing");
    } else {
        return Err(e);
    }
}

Prevention

When it happens

Trigger: Calling setup_shutdown_hook on Windows when GetModuleHandleW(std::ptr::null()) fails — e.g. the process image cannot be resolved (corrupted/unusual host process, limited execution context such as a service or embedded host where the module is not accessible), or Win32 returns an unexpected error at startup.

Common situations: Running the app inside an unusual Windows host (service host, non-standard launcher) where the executable module handle is unavailable; Windows API failures due to low resources or security software blocking module enumeration; running under constrained CI/service accounts.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/a27e4afe6eaca2b2. Report an issue: GitHub.