From 784c799a72c489ed53e2e4f91b803e8a8616c6e3 Mon Sep 17 00:00:00 2001 From: akbash-bot <300245827+akbash-bot@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:00:55 +0000 Subject: [PATCH 1/3] fix: synchronize plugin pane pwd refs #2984 --- docs/next/CHANGELOG.md | 1 + src/app/api/plugins/panes.rs | 61 +++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 55e129b051..9459c999d8 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -26,6 +26,7 @@ - Experimental pane graphics now support bounded named layers, acknowledged full-RGBA primary-layer direct file frames on audited local terminals, owned BGRA fallback, exact pixel mouse input, and placement-only resize replay. ### Fixed +- Unix plugin pane commands now receive `PWD` matching their resolved working directory, so direct popup tools open at explicit `--cwd` paths. (#2984) - High-rate output from many hidden panes no longer floods the server loop with redundant wakeups, and terminal input-mode synchronization no longer formats pane scrollback to read one keyboard flag. - Chinese IME commits now reach panes on macOS when the focused application requests printable key-release events. (#2924) - Windows now recognizes `Ctrl+1` through `Ctrl+9` keybindings instead of decoding those key records as control characters. (#2910) diff --git a/src/app/api/plugins/panes.rs b/src/app/api/plugins/panes.rs index 8b0749e392..2086abd68e 100644 --- a/src/app/api/plugins/panes.rs +++ b/src/app/api/plugins/panes.rs @@ -16,17 +16,17 @@ impl App { pane: PluginManifestPane, ) -> String { let context = self.current_plugin_context("plugin-pane"); + let cwd = self.plugin_pane_cwd(plugin, params.cwd); let extra_env = - match self.plugin_pane_launch_env(plugin, &pane.id, params.env.clone(), &context) { + match self.plugin_pane_launch_env(plugin, &pane.id, &cwd, params.env, &context) { Ok(env) => env, Err((code, message)) => return encode_error(id, &code, message), }; - let cwd = Some(self.plugin_pane_cwd(plugin, params.cwd)); let width = params.width.or(pane.width); let height = params.height.or(pane.height); if let Err(err) = self.spawn_popup_argv_command( &pane.command, - cwd, + Some(cwd), extra_env, crate::app::popup::PopupGeometry { width, height }, ) { @@ -49,17 +49,21 @@ impl App { pane: PluginManifestPane, ) -> String { let context = self.current_plugin_context("plugin-pane"); + let cwd = self.plugin_pane_cwd(plugin, params.cwd); let extra_env = - match self.plugin_pane_launch_env(plugin, &pane.id, params.env.clone(), &context) { + match self.plugin_pane_launch_env(plugin, &pane.id, &cwd, params.env, &context) { Ok(env) => env, Err((code, message)) => return encode_error(id, &code, message), }; - let cwd = Some(self.plugin_pane_cwd(plugin, params.cwd)); - let (ws_idx, new_pane) = - match self.spawn_overlay_argv_command(&pane.command, cwd, extra_env, Vec::new()) { - Ok(result) => result, - Err(err) => return encode_error(id, "plugin_pane_open_failed", err.to_string()), - }; + let (ws_idx, new_pane) = match self.spawn_overlay_argv_command( + &pane.command, + Some(cwd), + extra_env, + Vec::new(), + ) { + Ok(result) => result, + Err(err) => return encode_error(id, "plugin_pane_open_failed", err.to_string()), + }; let layout_tab_idx = self .overlay_panes .get(&new_pane.pane_id) @@ -98,8 +102,9 @@ impl App { ); }; let context = self.plugin_context_for_pane(ws_idx, target_pane, "plugin-pane"); + let cwd = self.plugin_pane_cwd(plugin, params.cwd); let extra_env = - match self.plugin_pane_launch_env(plugin, &pane.id, params.env.clone(), &context) { + match self.plugin_pane_launch_env(plugin, &pane.id, &cwd, params.env, &context) { Ok(env) => env, Err((code, message)) => return encode_error(id, &code, message), }; @@ -110,7 +115,6 @@ impl App { crate::api::schema::SplitDirection::Right => Direction::Horizontal, crate::api::schema::SplitDirection::Down => Direction::Vertical, }; - let cwd = Some(self.plugin_pane_cwd(plugin, params.cwd)); let (rows, cols) = self.state.estimate_pane_size(); let previous_focus = self.state.current_pane_focus_target(); let Some(ws) = self.state.workspaces.get_mut(ws_idx) else { @@ -121,7 +125,7 @@ impl App { direction, rows.max(4), cols.max(10), - cwd, + Some(cwd), &pane.command, extra_env, self.state.pane_scrollback_limit_bytes, @@ -187,7 +191,7 @@ impl App { let cwd = self.plugin_pane_cwd(plugin, params.cwd); let context = self.plugin_context_for_workspace(ws_idx, "plugin-pane"); let extra_env = - match self.plugin_pane_launch_env(plugin, &pane.id, params.env.clone(), &context) { + match self.plugin_pane_launch_env(plugin, &pane.id, &cwd, params.env, &context) { Ok(env) => env, Err((code, message)) => return encode_error(id, &code, message), }; @@ -233,10 +237,15 @@ impl App { &self, plugin: &InstalledPluginInfo, entrypoint: &str, + cwd: &std::path::Path, env: std::collections::HashMap, context: &PluginInvocationContext, ) -> Result, (String, String)> { let mut env = super::super::env::normalize_launch_env(env)?; + #[cfg(unix)] + set_default_plugin_pane_pwd(&mut env, cwd); + #[cfg(not(unix))] + let _ = cwd; let context_json = serde_json::to_string(&context) .map_err(|err| ("invalid_plugin_context".to_string(), err.to_string()))?; super::env::ensure_plugin_user_dirs(plugin) @@ -337,6 +346,13 @@ impl App { } } +#[cfg(unix)] +fn set_default_plugin_pane_pwd(env: &mut Vec<(String, String)>, cwd: &std::path::Path) { + if !env.iter().any(|(key, _)| key == "PWD") { + env.push(("PWD".to_string(), cwd.display().to_string())); + } +} + fn plugin_pane_protected_env_key(key: &str) -> bool { matches!( key, @@ -351,3 +367,20 @@ fn plugin_pane_protected_env_key(key: &str) -> bool { | "HERDR_BIN_PATH" ) } + +#[cfg(all(test, unix))] +mod tests { + use super::set_default_plugin_pane_pwd; + + #[test] + fn plugin_pane_pwd_defaults_to_cwd_without_overriding_explicit_env() { + let cwd = std::path::Path::new("/plugin-cwd"); + let mut derived = vec![("OTHER".to_string(), "value".to_string())]; + set_default_plugin_pane_pwd(&mut derived, cwd); + assert!(derived.contains(&("PWD".to_string(), "/plugin-cwd".to_string()))); + + let mut explicit = vec![("PWD".to_string(), "/caller-pwd".to_string())]; + set_default_plugin_pane_pwd(&mut explicit, cwd); + assert_eq!(explicit, [("PWD".to_string(), "/caller-pwd".to_string())]); + } +} From 00aa4f50d7c154dcc6724af431b53b6fd4c5128c Mon Sep 17 00:00:00 2001 From: akbash-bot <300245827+akbash-bot@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:18:44 +0000 Subject: [PATCH 2/3] refactor: isolate plugin pwd platform policy refs #2984 --- docs/next/CHANGELOG.md | 2 +- src/app/api/plugins/panes.rs | 29 +---------------------------- src/platform/fallback.rs | 10 ++++++++++ src/platform/linux.rs | 3 ++- src/platform/macos.rs | 3 ++- src/platform/unix_common.rs | 18 ++++++++++++++++++ src/platform/windows.rs | 6 ++++++ 7 files changed, 40 insertions(+), 31 deletions(-) diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 9459c999d8..094ccd4e48 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -26,7 +26,7 @@ - Experimental pane graphics now support bounded named layers, acknowledged full-RGBA primary-layer direct file frames on audited local terminals, owned BGRA fallback, exact pixel mouse input, and placement-only resize replay. ### Fixed -- Unix plugin pane commands now receive `PWD` matching their resolved working directory, so direct popup tools open at explicit `--cwd` paths. (#2984) +- Unix plugin pane commands now default `PWD` to their resolved working directory, so direct popup tools open at explicit `--cwd` paths while preserving caller-provided `PWD` values. (#2984) - High-rate output from many hidden panes no longer floods the server loop with redundant wakeups, and terminal input-mode synchronization no longer formats pane scrollback to read one keyboard flag. - Chinese IME commits now reach panes on macOS when the focused application requests printable key-release events. (#2924) - Windows now recognizes `Ctrl+1` through `Ctrl+9` keybindings instead of decoding those key records as control characters. (#2910) diff --git a/src/app/api/plugins/panes.rs b/src/app/api/plugins/panes.rs index 2086abd68e..8553ce91f7 100644 --- a/src/app/api/plugins/panes.rs +++ b/src/app/api/plugins/panes.rs @@ -242,10 +242,7 @@ impl App { context: &PluginInvocationContext, ) -> Result, (String, String)> { let mut env = super::super::env::normalize_launch_env(env)?; - #[cfg(unix)] - set_default_plugin_pane_pwd(&mut env, cwd); - #[cfg(not(unix))] - let _ = cwd; + crate::platform::set_default_plugin_pane_pwd(&mut env, cwd); let context_json = serde_json::to_string(&context) .map_err(|err| ("invalid_plugin_context".to_string(), err.to_string()))?; super::env::ensure_plugin_user_dirs(plugin) @@ -346,13 +343,6 @@ impl App { } } -#[cfg(unix)] -fn set_default_plugin_pane_pwd(env: &mut Vec<(String, String)>, cwd: &std::path::Path) { - if !env.iter().any(|(key, _)| key == "PWD") { - env.push(("PWD".to_string(), cwd.display().to_string())); - } -} - fn plugin_pane_protected_env_key(key: &str) -> bool { matches!( key, @@ -367,20 +357,3 @@ fn plugin_pane_protected_env_key(key: &str) -> bool { | "HERDR_BIN_PATH" ) } - -#[cfg(all(test, unix))] -mod tests { - use super::set_default_plugin_pane_pwd; - - #[test] - fn plugin_pane_pwd_defaults_to_cwd_without_overriding_explicit_env() { - let cwd = std::path::Path::new("/plugin-cwd"); - let mut derived = vec![("OTHER".to_string(), "value".to_string())]; - set_default_plugin_pane_pwd(&mut derived, cwd); - assert!(derived.contains(&("PWD".to_string(), "/plugin-cwd".to_string()))); - - let mut explicit = vec![("PWD".to_string(), "/caller-pwd".to_string())]; - set_default_plugin_pane_pwd(&mut explicit, cwd); - assert_eq!(explicit, [("PWD".to_string(), "/caller-pwd".to_string())]); - } -} diff --git a/src/platform/fallback.rs b/src/platform/fallback.rs index cdfbc1571a..64e5467b1d 100644 --- a/src/platform/fallback.rs +++ b/src/platform/fallback.rs @@ -3,6 +3,16 @@ use std::process::Command; use super::{ClipboardImage, ForegroundJob, Signal}; +#[cfg(unix)] +pub(crate) use super::unix_common::set_default_plugin_pane_pwd; + +#[cfg(not(unix))] +pub(crate) fn set_default_plugin_pane_pwd( + _env: &mut Vec<(String, String)>, + _cwd: &std::path::Path, +) { +} + pub(crate) fn remote_ssh_config_paths() -> super::RemoteSshConfigPaths { super::RemoteSshConfigPaths { user_config: std::env::var_os("HOME") diff --git a/src/platform/linux.rs b/src/platform/linux.rs index b5c332591a..fde57e57e3 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -16,7 +16,8 @@ pub(crate) use super::unix_common::{ configure_status_command, create_remote_private_dir, create_remote_ssh_config_dir, create_remote_ssh_config_file, hostname, local_datetime, remote_bridge_endpoint_path, remote_private_temp_base, remote_reattach_argument, remote_reattach_program, - remote_ssh_config_paths, status_commands_supported, StatusCommandGuard, + remote_ssh_config_paths, set_default_plugin_pane_pwd, status_commands_supported, + StatusCommandGuard, }; const WSL_MARKER_ENV_VARS: &[&str] = &["WSL_DISTRO_NAME", "WSL_INTEROP"]; diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 00fd245278..948468acbe 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -16,7 +16,8 @@ pub(crate) use super::unix_common::{ configure_status_command, create_remote_private_dir, create_remote_ssh_config_dir, create_remote_ssh_config_file, hostname, local_datetime, remote_bridge_endpoint_path, remote_private_temp_base, remote_reattach_argument, remote_reattach_program, - remote_ssh_config_paths, status_commands_supported, StatusCommandGuard, + remote_ssh_config_paths, set_default_plugin_pane_pwd, status_commands_supported, + StatusCommandGuard, }; const PROC_PGRP_ONLY: u32 = 2; diff --git a/src/platform/unix_common.rs b/src/platform/unix_common.rs index fa7b6d2009..1ab9b35ff2 100644 --- a/src/platform/unix_common.rs +++ b/src/platform/unix_common.rs @@ -209,10 +209,28 @@ fn datetime_from_tm(value: &libc::tm) -> Option { Some(time::PrimitiveDateTime::new(date, time)) } +pub(crate) fn set_default_plugin_pane_pwd(env: &mut Vec<(String, String)>, cwd: &std::path::Path) { + if !env.iter().any(|(key, _)| key == "PWD") { + env.push(("PWD".to_string(), cwd.display().to_string())); + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn plugin_pane_pwd_defaults_to_cwd_without_overriding_explicit_env() { + let cwd = Path::new("/plugin-cwd"); + let mut derived = vec![("OTHER".to_string(), "value".to_string())]; + set_default_plugin_pane_pwd(&mut derived, cwd); + assert!(derived.contains(&("PWD".to_string(), "/plugin-cwd".to_string()))); + + let mut explicit = vec![("PWD".to_string(), "/caller-pwd".to_string())]; + set_default_plugin_pane_pwd(&mut explicit, cwd); + assert_eq!(explicit, [("PWD".to_string(), "/caller-pwd".to_string())]); + } + #[test] fn remote_ssh_config_dir_rejects_overlong_control_socket_name() { let err = create_remote_ssh_config_dir(&"x".repeat(200)).unwrap_err(); diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 7aee384ede..5d5b014189 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -15,6 +15,12 @@ use std::{ mod clipboard_image; +pub(crate) fn set_default_plugin_pane_pwd( + _env: &mut Vec<(String, String)>, + _cwd: &std::path::Path, +) { +} + use windows_sys::{ Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation}, Win32::{ From 413eeae99d4d480ce152bbaddbec680e0e092ce6 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Thu, 20 Aug 2026 02:18:54 +0300 Subject: [PATCH 3/3] docs: fix changelog conflict resolution --- docs/next/CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 58f3a3ca91..a6d2b1ec79 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed - Tab bar status commands now remove ESC-prefixed terminal control sequences instead of displaying their sequence bodies as text. (#3001) +- Unix plugin pane commands now default `PWD` to their resolved working directory, so direct popup tools open at explicit `--cwd` paths while preserving caller-provided `PWD` values. (#2984) ## [0.8.2] - 2026-08-19 @@ -31,8 +32,6 @@ - Experimental pane graphics now support bounded named layers, acknowledged full-RGBA primary-layer direct file frames on audited local terminals, owned BGRA fallback, exact pixel mouse input, and placement-only resize replay. ### Fixed -- Unix plugin pane commands now default `PWD` to their resolved working directory, so direct popup tools open at explicit `--cwd` paths while preserving caller-provided `PWD` values. (#2984) -- High-rate output from many hidden panes no longer floods the server loop with redundant wakeups, and terminal input-mode synchronization no longer formats pane scrollback to read one keyboard flag. - Live handoff now preserves mouse forwarding for running pane applications. (#3000, thanks @xkrogen) - Unix CLI commands now exit quietly when a downstream pipe closes instead of panicking with exit 101. (#2994) - The terminal theme now keeps the active Space row fill visible when the Navigate cursor lands on it, in both expanded and collapsed sidebars. (#2987)