Skip to content

feat: ExternalTool wrappers for git, gh, rustc commands#2031

Closed
aboimpinto wants to merge 21 commits into
Hmbown:mainfrom
aboimpinto:feat/external-tool-on-sh
Closed

feat: ExternalTool wrappers for git, gh, rustc commands#2031
aboimpinto wants to merge 21 commits into
Hmbown:mainfrom
aboimpinto:feat/external-tool-on-sh

Conversation

@aboimpinto
Copy link
Copy Markdown
Contributor

Stacked on ShellDispatcher (#2008). Replaces hardcoded Command::new calls with ExternalTool wrappers in main.rs.

aboimpinto added 21 commits May 24, 2026 19:51
Introduces a central shell abstraction that replaces hardcoded
Command::new("cmd") / Command::new("sh") across the execution path.

- Shell detection at startup (pwsh -> powershell -> cmd -> sh)
- Correct quoting per shell (PowerShell uses -NoProfile -Command)
- Scope guards restore crossterm raw mode on all spawn paths (Hmbown#1690)
- Direct program+args builder for sandbox ExecEnv integration

Files:
- crates/tui/src/shell_dispatcher.rs (new, 12 tests)
- crates/tui/src/main.rs (register module)
- crates/tui/src/eval.rs (exec_shell delegates to dispatcher)
- crates/tui/src/sandbox/mod.rs (CommandSpec::shell uses dispatcher)
- crates/tui/src/tools/shell.rs (raw mode guards on all spawn paths)

Closes Hmbown#1690
Refs Hmbown#1779
… logging

- find_exe(): fall back to known install dirs when PATH lookup fails
  (C:\Program Files\PowerShell\7, System32\WindowsPowerShell\v1.0)
- Encoding prefix: use idiomatic [Console]::OutputEncoding for PowerShell
  instead of cmd.exe chcp 65001 >NUL
- Execution logging: write exec via <ShellKind> entries to
  SHELL_DISPATCHER_LOG when set
- System prompt: use ShellDispatcher detection instead of raw $SHELL
  so model knows it has PowerShell and generates native cmdlets
- display_command(): strip PowerShell encoding prefix for display
- Add tests for find_exe PATH + known-dir fallback

Refs Hmbown#1779
- Fixed sandbox tests to accept sh/bash/zsh on non-Windows
- Added cfg_shell_program() helper that uses ShellDispatcher detection
- Marked find_exe_finds_cmd_on_path as #[cfg(windows)]
- Fixed shell::tests for issue_1691 to accept sh/bash/zsh
- Added #[cfg(windows)] to cmd-specific tests
ShellDispatcher may detect pwsh, powershell, or cmd on Windows.
Tests now verify the command is passed as the last arg instead of
hardcoding cmd.exe with chcp prefix.
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a ShellDispatcher abstraction to centralize shell detection and command execution across different platforms and shell types (PowerShell, Cmd, Bash, etc.). It updates various components, including rustc_version detection, GitHub CLI interactions, and the sandbox environment, to utilize this new dispatcher. Feedback identifies critical issues with terminal state management where raw mode is unconditionally restored even if it wasn't initially active, potentially corrupting the terminal in non-TUI modes. Additionally, there is a concurrency concern regarding unhandled mutex poisoning in the logging logic which could lead to race conditions.

Comment on lines +840 to +849
// Disable raw mode before spawn; restore on drop regardless of
// success/failure/timeout (issue #1690).
let _ = crossterm::terminal::disable_raw_mode();
struct SyncRawModeGuard;
impl Drop for SyncRawModeGuard {
fn drop(&mut self) {
let _ = crossterm::terminal::enable_raw_mode();
}
}
let _guard = SyncRawModeGuard;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The SyncRawModeGuard unconditionally enables terminal raw mode when dropped. This causes a regression in non-TUI modes (such as codewhale exec), where raw mode is not initially active; the guard will leave the user's terminal in raw mode after the command finishes, breaking normal shell behavior (e.g., missing newlines, no echo). The guard should only restore raw mode if it was previously enabled, matching the pattern used in ShellDispatcher::run_foreground.

Comment on lines +985 to +993
// Disable raw mode before spawn; restore on drop (issue #1690).
let _ = crossterm::terminal::disable_raw_mode();
struct InteractiveRawModeGuard;
impl Drop for InteractiveRawModeGuard {
fn drop(&mut self) {
let _ = crossterm::terminal::enable_raw_mode();
}
}
let _guard = InteractiveRawModeGuard;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to SyncRawModeGuard, InteractiveRawModeGuard unconditionally enables raw mode on drop. This will corrupt the terminal state if the manager is invoked from a context where raw mode was not already active (e.g., CLI mode). It should be updated to check the initial state and only restore if necessary.

}

fn log_startup(kind: &ShellKind) {
let _lock = LOG_MUTEX.lock();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The result of LOG_MUTEX.lock() is not handled for poisoning. If the mutex becomes poisoned (e.g., due to a panic while holding the lock), lock() returns an Err variant. Since the result is assigned to _lock without unwrapping or extracting the guard, the current thread will proceed into the critical section without actually holding the lock, leading to potential race conditions when writing to the log file. This pattern is repeated at lines 162 and 231.

Copy link
Copy Markdown
Contributor Author

Closing this PR because I folded the stacked work into #1848 to avoid presenting overlapping PRs as independently mergeable.

The ExternalTool wrapper changes from this PR are included in #1848.

Paulo Aboim Pinto

@aboimpinto aboimpinto closed this May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant