{"record":{"id":"b57c74c3bc43b6af","repo":"sinelaw/fresh","slug":"plugin-thread-closed","errorCode":null,"errorMessage":"Plugin thread closed","messagePattern":"Plugin thread closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-plugin-runtime/src/thread.rs","lineNumber":581,"sourceCode":"                    resource,\n                }));\n            }\n        }\n    }\n\n    /// Load a plugin from a file (blocking)\n    pub fn load_plugin(&self, path: &Path) -> Result<()> {\n        let (tx, rx) = oneshot::channel();\n        self.request_sender\n            .as_ref()\n            .ok_or_else(|| anyhow!(\"Plugin thread shut down\"))?\n            .send(PluginRequest::LoadPlugin {\n                path: path.to_path_buf(),\n                response: tx,\n            })\n            .map_err(|_| anyhow!(\"Plugin thread not responding\"))?;\n\n        rx.recv().map_err(|_| anyhow!(\"Plugin thread closed\"))?\n    }\n\n    /// Load all plugins from a directory (blocking)\n    pub fn load_plugins_from_dir(&self, dir: &Path) -> Vec<String> {\n        let (tx, rx) = oneshot::channel();\n        let Some(sender) = self.request_sender.as_ref() else {\n            return vec![\"Plugin thread shut down\".to_string()];\n        };\n        if sender\n            .send(PluginRequest::LoadPluginsFromDir {\n                dir: dir.to_path_buf(),\n                response: tx,\n            })\n            .is_err()\n        {\n            return vec![\"Plugin thread not responding\".to_string()];\n        }\n","sourceCodeStart":563,"sourceCodeEnd":599,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-plugin-runtime/src/thread.rs#L563-L599","documentation":"After successfully enqueueing the LoadPlugin request, load_plugin() blocks on the oneshot receiver `rx.recv()`. This error means the request was sent but the response half was dropped before a reply arrived — the plugin thread stopped (shutdown, panic, or loop exit) while the load was being processed. The plugin was therefore not loaded and no per-plugin error is available.","triggerScenarios":"The plugin thread processes (or is mid-processing) a LoadPlugin request and then exits — e.g. `shutdown()` is invoked from another thread, or the worker loop returns/panics — dropping the response sender without replying.","commonSituations":"User quits the editor while a plugin is still loading; a panic inside the worker's plugin evaluation kills the thread; long plugin loads (heavy JS/TS source) that overlap application teardown.","solutions":["Check worker thread logs/panic hooks to find why the plugin thread exited mid-request (a panicking plugin is the usual culprit).","Re-issue the load after restarting the plugin thread if the exit was due to a panicking plugin; consider isolating plugin execution from the request loop.","Avoid calling shutdown() while loads are pending; use a completion signal/join handle before teardown.","Treat this error as terminal for the request and report that the plugin was NOT loaded."],"exampleFix":"// before\nlet handle = thread::spawn(move || runtime.load_plugin(&path).unwrap()); // ignores shutdown ordering\n\n// after\nshutdown_tx.send(());\nworker_handle.join().expect(\"plugin thread panicked\"); // join BEFORE assuming loads finished\n// then, on a live runtime:\nruntime.load_plugin(&path)?;","handlingStrategy":"try-catch","validationCode":"// Track pending requests and only shut down when zero\nfn safe_shutdown(rt: &PluginRuntimeHandle, pending: &AtomicUsize) {\n    if pending.load(Ordering::SeqCst) == 0 {\n        rt.shutdown();\n    }\n}","typeGuard":"fn got_reply(r: &Result<PluginResponse, RecvError>) -> bool { r.is_ok() }","tryCatchPattern":"match rt.load_plugin(&path) {\n    Err(e) if e.to_string().contains(\"Plugin thread closed\") => {\n        error!(\"worker died while loading {}; plugin NOT loaded\", path.display());\n        rt.restart_thread()?;\n    }\n    other => other?,\n}","preventionTips":["Isolate plugin evaluation so a panicking plugin cannot kill the request loop (catch_unwind around execution).","Block shutdown until all pending loads resolve; use a request counter or join handle.","Enable worker panic logging to diagnose mid-request deaths.","Keep plugin loads short; avoid heavy transpilation directly inside the worker's critical loop."],"tags":["plugin-runtime","channel","worker-panic","threading"],"backgroundTag":"thread-interrupted","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}