{"record":{"id":"7c3306b8ea6da7b3","repo":"sinelaw/fresh","slug":"plugin-thread-not-responding","errorCode":null,"errorMessage":"Plugin thread not responding","messagePattern":"Plugin thread not responding","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-plugin-runtime/src/thread.rs","lineNumber":579,"sourceCode":"                fire_and_forget(sender.send(PluginRequest::TrackAsyncResource {\n                    plugin_name,\n                    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()];","sourceCodeStart":561,"sourceCodeEnd":597,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-plugin-runtime/src/thread.rs#L561-L597","documentation":"load_plugin() sends its LoadPlugin request through a bounded channel to the plugin worker thread. This error is raised when `sender.send(...)` fails, which for a channel paired with a oneshot reply means the worker's receiver end has been dropped — the thread is no longer processing requests even though the sender handle still exists. It is distinct from \"Plugin thread shut down\" (sender is None): here the handle looks alive but the worker is gone.","triggerScenarios":"Calling `load_plugin(path)` at the moment the plugin thread's request receiver has been dropped — typically during or immediately after shutdown, or after the worker loop exited via panic/error while the handle still holds a sender clone.","commonSituations":"Race between an editor shutdown sequence and a concurrently running plugin loader; a plugin thread crash that closed the channel without clearing the handle's sender; calling into the runtime from another thread right as the worker exits.","solutions":["Verify no code path terminates the plugin thread while loads are in flight; sequence shutdown after all load calls complete.","Retry once after confirming the runtime is still running — if the thread panicked, restart the plugin thread and re-issue the load.","Log at the call site whether the runtime was concurrently shut down to find the racy caller.","Wrap the runtime in an Arc and keep it alive for all callers so the worker isn't dropped mid-request."],"exampleFix":"// before\nruntime.load_plugin(&path)?; // may race with shutdown\n\n// after\nmatch runtime.load_plugin(&path) {\n    Ok(()) => info!(\"plugin loaded\"),\n    Err(e) if e.to_string().contains(\"not responding\") => {\n        warn!(\"plugin thread went away during load; restarting runtime\");\n        runtime.restart_thread()?;\n        runtime.load_plugin(&path)?;\n    }\n    Err(e) => return Err(e),\n}","handlingStrategy":"retry","validationCode":"// Send-probe before real work\nfn thread_alive(rt: &PluginRuntimeHandle) -> bool {\n    // e.g. a cheap status request that round-trips through the channel\n    rt.ping().is_ok()\n}","typeGuard":"fn channel_open(r: &Result<(), SendError<PluginRequest>>) -> bool { r.is_ok() }","tryCatchPattern":"match rt.load_plugin(&path) {\n    Err(e) if e.to_string().contains(\"not responding\") => {\n        warn!(\"plugin worker dropped receiver; restarting once\");\n        rt.restart_thread()?;\n        rt.load_plugin(&path)\n    }\n    other => other,\n}","preventionTips":["Join the worker thread before assuming the runtime is usable after a previous crash.","Install a panic hook on the worker so thread death is logged loudly.","Sequence shutdown after all in-flight loads complete.","Keep the runtime in an Arc shared by all callers so it cannot be dropped mid-request."],"tags":["plugin-runtime","channel","shutdown-race","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"}