From 37c9b46a51b22d599c8355566b7d766dd42f8475 Mon Sep 17 00:00:00 2001 From: Vantz Stockwell Date: Tue, 24 Mar 2026 21:13:42 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20PTY=20crash=20=E2=80=94=20use=20std::thr?= =?UTF-8?q?ead::spawn=20instead=20of=20tokio::spawn=5Fblocking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tokio::task::spawn_blocking panics when called without a tokio runtime context. Sync Tauri command handlers may not have one. The PTY reader loop is long-lived anyway — std::thread::spawn is more correct for persistent blocking I/O and doesn't require a runtime guard. Co-Authored-By: Claude Opus 4.6 (1M context) --- src-tauri/src/pty/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/pty/mod.rs b/src-tauri/src/pty/mod.rs index 5ca6a61..6034f83 100644 --- a/src-tauri/src/pty/mod.rs +++ b/src-tauri/src/pty/mod.rs @@ -112,11 +112,13 @@ impl PtyService { self.sessions.insert(session_id.clone(), session); - // Output reader loop — runs in a blocking thread because - // portable-pty's reader is synchronous (std::io::Read). + // Output reader loop — runs in a dedicated OS thread because + // portable-pty's reader is synchronous (std::io::Read) and + // long-lived. Using std::thread::spawn avoids requiring a + // tokio runtime context (sync Tauri commands may not have one). let sid = session_id.clone(); let app = app_handle; - tokio::task::spawn_blocking(move || { + std::thread::spawn(move || { let mut reader = std::io::BufReader::new(reader); let mut buf = [0u8; 4096]; loop {