libnyanpasu/clash-nyanpasu · error

Can't create listener

Error message

Can't create listener

What it means

listen() binds a Unix domain socket at /tmp/{app-id}-deep-link.sock and .expects the bind to succeed, panicking with "Can't create listener" on failure. Bind fails when the path is invalid, a stale socket file exists without cleanup in a way that blocks bind, the tmp directory is unwritable, or another process owns the socket.

Source

Thrown at backend/tauri-plugin-deep-link/src/linux.rs:110

                "Couldn't get file name of current executable.",
            ))?
            .to_string_lossy()
    ));

    remove_file(&target)?;
    target.pop();

    Ok(())
}

pub fn listen<F: FnMut(String) + Send + 'static>(mut handler: F) -> Result<()> {
    std::thread::spawn(move || {
        let addr = format!(
            "/tmp/{}-deep-link.sock",
            ID.get().expect("listen() called before prepare()")
        );

        let listener = UnixListener::bind(addr).expect("Can't create listener");

        for stream in listener.incoming() {
            match stream {
                Ok(mut stream) => {
                    let mut buffer = String::new();
                    if let Err(io_err) = stream.read_to_string(&mut buffer) {
                        log::error!("Error reading incoming connection: {}", io_err.to_string());
                    };

                    handler(dbg!(buffer));
                }
                Err(err) => {
                    log::error!("Incoming connection failed: {}", err);
                    continue;
                }
            }
        }
    });

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Ensure only one app instance runs, or forward deep-link URLs to the existing instance instead of binding again.
  2. Delete the stale /tmp/{app-id}-deep-link.sock file before starting if the previous run crashed.
  3. Check /tmp permissions (writable by the app user) and sandbox/container mount settings.
  4. Shorten the app ID if the socket path exceeds ~108 characters.
  5. Patch the plugin to handle bind errors gracefully (return Err / retry with cleanup) instead of expect.

Example fix

// before: blind bind that panics on stale socket
let listener = UnixListener::bind(&addr).expect("Can't create listener");

// after: clean up stale socket, then bind
let _ = std::fs::remove_file(&addr); // ignore Not-Found
let listener = UnixListener::bind(&addr).expect("Can't create listener");
Defensive patterns

Strategy: validation

Validate before calling

let addr = format!("/tmp/{}-deep-link.sock", APP_ID);
let _ = std::fs::remove_file(&addr); // clear stale socket
assert!(addr.len() <= 107, "socket path too long");

Try / catch

match UnixListener::bind(&addr) { Ok(l) => ..., Err(e) if e.kind() == io::ErrorKind::AddrInUse => eprintln!("another instance running"), Err(e) => return Err(e.into()) }

Prevention

When it happens

Trigger: UnixListener::bind fails because another instance of the app already holds the deep-link socket, /tmp is not writable, a leftover socket file with wrong permissions exists at the path, or the app ID contains characters that make the path invalid/too long.

Common situations: Running a second instance of the app while the first is still alive; containers/sandboxes with read-only or restricted /tmp; crashes that left a stale socket file; very long app IDs exceeding the 108-byte Unix socket path limit.

Related errors


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