libnyanpasu/clash-nyanpasu · error

listen() called before prepare()

Error message

listen() called before prepare()

What it means

listen() on Windows converts the identifier from the ID OnceCell into a local-socket name. If prepare() was not called first, ID.get() returns None and this expect() panics. It enforces the prepare() -> listen() lifecycle order.

Source

Thrown at backend/tauri-plugin-deep-link/src/windows.rs:82

        let base = Path::new("Software").join("Classes").join(scheme);

        hkcu.delete_subkey_all(base)?;
    }

    Ok(())
}

static CRASH_COUNT: AtomicU16 = AtomicU16::new(0);

pub fn listen<F: FnMut(String) + Send + 'static>(mut handler: F) -> Result<()> {
    if CRASH_COUNT.load(Ordering::Acquire) > 5 {
        panic!("Local socket too many crashes");
    }

    std::thread::spawn(move || {
        let name = ID
            .get()
            .expect("listen() called before prepare()")
            .as_str()
            .to_ns_name::<GenericNamespaced>()
            .unwrap();
        tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .expect("failed to create tokio runtime")
            .block_on(async move {
                let sdsf = "D:(A;;GA;;;WD)".to_wtf_16().unwrap();
                let sd = SecurityDescriptor::deserialize(&sdsf).expect("Failed to deserialize SD");
                let listener = ListenerOptions::new()
                    .name(name)
                    .nonblocking(ListenerNonblockingMode::Both)
                    .security_descriptor(sd)
                    .create_tokio()
                    .expect("Can't create listener");

                loop {

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Ensure prepare(identifier) runs before listen() on every startup path
  2. Centralize deep-link init (prepare + listen) in one function
  3. Add an assertion/early check that identifier config is present before starting deep-link handling

Example fix

// before
listen(handler)?;
// after
prepare("com.example.app");
listen(handler)?;
Defensive patterns

Strategy: validation

Validate before calling

assert!(ID.get().is_some(), "call prepare() before listen()");

Type guard

fn is_prepared() -> bool { ID.get().is_some() }

Prevention

When it happens

Trigger: listen() invoked before prepare(); a second registration path calling listen() without prepare(); single-instance handoff code executing before plugin setup.

Common situations: App bootstrap ordering mistakes; two listen() call sites where one path lacks prepare(); dev/test harnesses skipping plugin initialization.

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/d407ee7f4b7b663b. Report an issue: GitHub.