{"record":{"id":"b3029b107224ecc3","repo":"libnyanpasu/clash-nyanpasu","slug":"listen-called-before-prepare-b3029b","errorCode":null,"errorMessage":"listen() called before prepare()","messagePattern":"listen\\(\\) called before prepare\\(\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri-plugin-deep-link/src/macos.rs","lineNumber":105,"sourceCode":"        ID.get()\n            .expect(\"URL event received before prepare() was called\")\n    );\n    if let Ok(mut stream) = UnixStream::connect(addr) {\n        if let Err(io_err) = stream.write_all(s.as_bytes()) {\n            log::error!(\n                \"Error sending message to primary instance: {}\",\n                io_err.to_string()\n            );\n        };\n    }\n    std::process::exit(0);\n}\n\npub fn listen<F: FnMut(String) + Send + 'static>(handler: F) -> Result<()> {\n    #[cfg(debug_assertions)]\n    let addr = format!(\n        \"/tmp/{}-deep-link.sock\",\n        ID.get().expect(\"listen() called before prepare()\")\n    );\n\n    #[cfg(debug_assertions)]\n    if HANDLER\n        .set(match UnixStream::connect(&addr) {\n            Ok(_) => Mutex::new(Box::new(secondary_handler)),\n            Err(err) => {\n                log::error!(\"Error creating socket listener: {}\", err.to_string());\n                if err.kind() == ErrorKind::ConnectionRefused {\n                    let _ = remove_file(&addr);\n                }\n                Mutex::new(Box::new(handler))\n            }\n        })\n        .is_err()\n    {\n        return Err(std::io::Error::new(\n            ErrorKind::AlreadyExists,","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri-plugin-deep-link/src/macos.rs#L87-L123","documentation":"listen() on macOS reads the identifier from the ID OnceCell to build the debug Unix socket path. If prepare() was not called first, ID.get() is None and this expect() panics. It is a lifecycle-order guard: prepare() must always precede listen().","triggerScenarios":"Calling listen(handler) before prepare(identifier); conditional startup logic that registers deep links but skips prepare(); re-entrancy where listen is invoked from a hook that runs before plugin prepare.","commonSituations":"Copy-pasted plugin setup missing the prepare() call; app refactors that moved listen() earlier than prepare(); tests exercising listen() in isolation.","solutions":["Always call prepare(identifier) before listen()","Add prepare() at the top of your deep-link registration helper so ordering cannot regress","In tests, call prepare() in a setup fixture before touching listen()"],"exampleFix":"// before\nlisten(|url| println!(\"{}\", url))?;\n// after\nprepare(\"com.example.app\");\nlisten(|url| println!(\"{}\", url))?;","handlingStrategy":"validation","validationCode":"assert!(ID.get().is_some(), \"call prepare() before listen()\");","typeGuard":"fn is_prepared() -> bool { ID.get().is_some() }","tryCatchPattern":null,"preventionTips":["Enforce prepare-then-listen ordering in one setup function","Do not call listen() from hooks that run before plugin prepare","Add an init-order integration test"],"tags":["rust","initialization","panic","deep-link","macos"],"backgroundTag":"invalid-state-transition","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}