diff --git a/rust/core/src/clients/local.rs b/rust/core/src/clients/local.rs
index 7350194..7732b8d 100644
--- a/rust/core/src/clients/local.rs
+++ b/rust/core/src/clients/local.rs
@@ -60,7 +60,8 @@ impl LocalShell {
.map_err(|e| format!("failed to start shell {:?}: {e}", self.shell))?;
let mut cmd = CommandBuilder::new(&self.shell);
cmd.env_clear();
- let env = crate::terminalenv::local_shell_env(std::env::vars().map(|(k, v)| format!("{k}={v}")).collect());
+ // `vars()` panics on a non-UTF-8 entry (a launcher can plant one); skip those instead.
+ let env = crate::terminalenv::local_shell_env(std::env::vars_os().filter_map(|(k, v)| Some(format!("{}={}", k.to_str()?, v.to_str()?))).collect());
for kv in env {
if let Some((k, v)) = kv.split_once('=') {
cmd.env(k, v);
diff --git a/rust/core/src/clients/mod.rs b/rust/core/src/clients/mod.rs
index 6caa286..fad40db 100644
--- a/rust/core/src/clients/mod.rs
+++ b/rust/core/src/clients/mod.rs
@@ -105,22 +105,31 @@ pub(crate) fn dial(host: &str, port: i64, timeout_secs: i64) -> (String, std::io
/// reports `read error: …` then closes; `close` is idempotent and fires `on_close` once).
pub(crate) struct TcpConn {
stream: parking_lot::Mutex