libnyanpasu/clash-nyanpasu · error · std::io::Error (AlreadyExists)

Handler was already set

Error message

Handler was already set

What it means

On macOS the deep-link plugin stores the global event handler in a `OnceLock`-like `HANDLER` static. `listen` tries to install the handler with a debug-only and a release-mode set; the first (cfg(debug_assertions)) attempt fails if a handler was already registered, producing this `AlreadyExists` io::Error. It means the app is trying to register a second deep-link listener.

Source

Thrown at backend/tauri-plugin-deep-link/src/macos.rs:122

        "/tmp/{}-deep-link.sock",
        ID.get().expect("listen() called before prepare()")
    );

    #[cfg(debug_assertions)]
    if HANDLER
        .set(match UnixStream::connect(&addr) {
            Ok(_) => Mutex::new(Box::new(secondary_handler)),
            Err(err) => {
                log::error!("Error creating socket listener: {}", err.to_string());
                if err.kind() == ErrorKind::ConnectionRefused {
                    let _ = remove_file(&addr);
                }
                Mutex::new(Box::new(handler))
            }
        })
        .is_err()
    {
        return Err(std::io::Error::new(
            ErrorKind::AlreadyExists,
            "Handler was already set",
        ));
    }

    #[cfg(not(debug_assertions))]
    if HANDLER.set(Mutex::new(Box::new(handler))).is_err() {
        return Err(std::io::Error::new(
            ErrorKind::AlreadyExists,
            "Handler was already set",
        ));
    }

    unsafe {
        let event_manager: Retained<AnyObject> =
            msg_send_id![class!(NSAppleEventManager), sharedAppleEventManager];

        let handler = Handler::new();

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Register the deep-link handler exactly once, in the tauri `setup` hook.
  2. Guard the call with a Once/flag or check whether a handler is already installed before calling listen.
  3. Ignore ErrorKind::AlreadyExists if a second registration attempt is harmless in your flow.

Example fix

// before
app.listen("myapp", |event| { ... }); // also called elsewhere
// after
static LISTENER: Once = Once::new();
LISTENER.call_once(|| { app.listen("myapp", |event| { ... }); });
Defensive patterns

Strategy: try-catch

Validate before calling

// static guard
static REGISTERED: AtomicBool = AtomicBool::new(false);
if REGISTERED.swap(true, Ordering::SeqCst) { return; } // already registered

Try / catch

match register(app) {
    Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}, // handler already installed
    other => other?,
}

Prevention

When it happens

Trigger: Calling `register`/`listen` twice in a debug build — e.g. registering in both the tauri setup hook and a window event, or on app re-init — while HANDLER already holds a handler.

Common situations: Double registration during development hot-reload, calling listen from multiple windows, or plugin setup code executed more than once.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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