{"record":{"id":"d407ee7f4b7b663b","repo":"libnyanpasu/clash-nyanpasu","slug":"listen-called-before-prepare-d407ee","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/windows.rs","lineNumber":82,"sourceCode":"        let base = Path::new(\"Software\").join(\"Classes\").join(scheme);\n\n        hkcu.delete_subkey_all(base)?;\n    }\n\n    Ok(())\n}\n\nstatic CRASH_COUNT: AtomicU16 = AtomicU16::new(0);\n\npub fn listen<F: FnMut(String) + Send + 'static>(mut handler: F) -> Result<()> {\n    if CRASH_COUNT.load(Ordering::Acquire) > 5 {\n        panic!(\"Local socket too many crashes\");\n    }\n\n    std::thread::spawn(move || {\n        let name = ID\n            .get()\n            .expect(\"listen() called before prepare()\")\n            .as_str()\n            .to_ns_name::<GenericNamespaced>()\n            .unwrap();\n        tokio::runtime::Builder::new_current_thread()\n            .enable_all()\n            .build()\n            .expect(\"failed to create tokio runtime\")\n            .block_on(async move {\n                let sdsf = \"D:(A;;GA;;;WD)\".to_wtf_16().unwrap();\n                let sd = SecurityDescriptor::deserialize(&sdsf).expect(\"Failed to deserialize SD\");\n                let listener = ListenerOptions::new()\n                    .name(name)\n                    .nonblocking(ListenerNonblockingMode::Both)\n                    .security_descriptor(sd)\n                    .create_tokio()\n                    .expect(\"Can't create listener\");\n\n                loop {","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri-plugin-deep-link/src/windows.rs#L64-L100","documentation":"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.","triggerScenarios":"listen() invoked before prepare(); a second registration path calling listen() without prepare(); single-instance handoff code executing before plugin setup.","commonSituations":"App bootstrap ordering mistakes; two listen() call sites where one path lacks prepare(); dev/test harnesses skipping plugin initialization.","solutions":["Ensure prepare(identifier) runs before listen() on every startup path","Centralize deep-link init (prepare + listen) in one function","Add an assertion/early check that identifier config is present before starting deep-link handling"],"exampleFix":"// before\nlisten(handler)?;\n// after\nprepare(\"com.example.app\");\nlisten(handler)?;","handlingStrategy":"validation","validationCode":"assert!(ID.get().is_some(), \"call prepare() before listen()\");","typeGuard":"fn is_prepared() -> bool { ID.get().is_some() }","tryCatchPattern":null,"preventionTips":["Audit all listen() call sites for a preceding prepare()","Centralize deep-link initialization","Test each startup path (normal, single-instance handoff, tests)"],"tags":["rust","windows","deep-link","initialization"],"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"}