{"record":{"id":"a94cb711338ef7b3","repo":"zed-industries/zed","slug":"main-thread-message-channel-should-not-be-closed-y","errorCode":null,"errorMessage":"main thread message channel should not be closed yet, extension {} (id {})","messagePattern":"main thread message channel should not be closed yet, extension (.+?) \\(id (.+?)\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/extension_host/src/wasm_host.rs","lineNumber":959,"sourceCode":"    }\n}\n\nimpl WasmState {\n    fn on_main_thread<T, Fn>(&self, f: Fn) -> impl 'static + Future<Output = T>\n    where\n        T: 'static + Send,\n        Fn: 'static + Send + for<'a> FnOnce(&'a mut AsyncApp) -> LocalBoxFuture<'a, T>,\n    {\n        let (return_tx, return_rx) = oneshot::channel();\n        self.host\n            .main_thread_message_tx\n            .clone()\n            .unbounded_send(Box::new(move |cx| {\n                async {\n                    let result = f(cx).await;\n                    return_tx.send(result).ok();\n                }\n                .boxed_local()\n            }))\n            .unwrap_or_else(|_| {\n                panic!(\n                    \"main thread message channel should not be closed yet, extension {} (id {})\",\n                    self.manifest.name, self.manifest.id,\n                )\n            });\n        let name = self.manifest.name.clone();\n        let id = self.manifest.id.clone();\n        async move {\n            return_rx.await.unwrap_or_else(|_| {\n                panic!(\"main thread message channel, extension {name} (id {id})\")\n            })\n        }\n    }\n\n    fn work_dir(&self) -> PathBuf {\n        self.host.work_dir.join(self.manifest.id.as_ref())","sourceCodeStart":941,"sourceCodeEnd":977,"githubUrl":"https://github.com/zed-industries/zed/blob/5a9b9558db01a6b906cec2fb70a797affdc58cdd/crates/extension_host/src/wasm_host.rs#L941-L977","documentation":"WasmHost::run_on_main_thread posts a closure to the main thread over an unbounded channel. Sending can only fail once the receiving end has been dropped — i.e. the application is tearing down. The code asserts extensions cannot be running at that point and panics, naming the extension and its id.","triggerScenarios":"An extension invokes an API that proxies to the main thread (most extension host APIs do) during or after app shutdown, after the main-thread message loop was dropped but before the extension was fully deactivated.","commonSituations":"Long-running extension tasks (timers, background HTTP requests) completing just as the window/app quits; shutdown-ordering races between the extension host and GPUI's main loop.","solutions":["Update Zed — shutdown-ordering fixes in extension_host land regularly and this class of panic is treated as a bug","As an extension author, cancel timers/requests in deactivate and never start new API calls after deactivation","Report the panic with the extension name/id shown in the message and repro steps (usually 'quit while extension X was active')"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Extension authors: cancel timers and in-flight requests in deactivate; start no new API calls afterwards","Keep extension background work short-lived so it rarely straddles app quit","Update Zed regularly: shutdown ordering between extension_host and the main loop is actively hardened"],"tags":["zed","extension-host","wasm","channel","shutdown","panic"],"backgroundTag":"send-on-closed-channel","analyzedSha":"5a9b9558db01a6b906cec2fb70a797affdc58cdd","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}