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
- Register the deep-link handler exactly once, in the tauri `setup` hook.
- Guard the call with a Once/flag or check whether a handler is already installed before calling listen.
- 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
- Call listen/register only once, from the tauri setup hook
- Use a Once guard around registration
- Don't re-register on window recreation or hot reload
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
- URL event received before prepare() was called
- listen() called before prepare()
- Can't create listener
- prepare() called more than once with different identifiers.
- Local socket too many crashes
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/6a4cfe831c404b9d.
Report an issue: GitHub.