Skip to content

fix(commands): resolve relative external command paths against shell cwd - #1252

Open
DaZuo0122 wants to merge 4 commits into
reubeno:mainfrom
DaZuo0122:main
Open

DaZuo0122 wants to merge 4 commits into
reubeno:mainfrom
DaZuo0122:main

Conversation

@DaZuo0122

Copy link
Copy Markdown

Fixes #1251.

On Windows, relative external command paths were resolved against the
brush process's OS current directory instead of the shell's working
directory. This caused commands such as:

cd target
./release/brush.exe --version

to fail with ERROR_PATH_NOT_FOUND, even though the executable existed
relative to the shell's current working directory.

Fix

Resolve relative executable paths against context.shell.working_dir()
before composing the std::process::Command.

Absolute paths and PATH-resolved commands continue to work.

p.s. I have added #[allow(clippy::too_many_lines)] to execute_external_command to pass the cargo xtask ci pre-commit check.

Absolutize relative executable paths against the shell working directory
before spawning external commands.

Fixes reubeno#1251
@reubeno

reubeno commented Aug 8, 2026

Copy link
Copy Markdown
Owner

@DaZuo0122 Thanks for the contribution (and for finding the issue)!

I'd like to find a way to resolve this without making any meaningful behavioral changes to non-Windows code paths. (We can also reuse the path normalization helper crate that we use elsewhere in the project -- that may help to simplify.)

Are you open to me collaborating with you on this and refactoring it a bit?

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Performance Benchmark Report

Benchmark name Baseline (μs) Test/PR (μs) Delta (μs) Delta %
clone_shell_object 19.36 μs 19.31 μs -0.04 μs ⚪ Unchanged
eval_arithmetic 0.13 μs 0.13 μs 0.00 μs ⚪ Unchanged
expand_one_string 1.58 μs 1.59 μs 0.01 μs ⚪ Unchanged
for_loop 28.08 μs 28.52 μs 0.43 μs 🟠 +1.54%
full_peg_complex 55.34 μs 55.70 μs 0.36 μs ⚪ Unchanged
full_peg_for_loop 6.22 μs 6.30 μs 0.08 μs 🟠 +1.29%
full_peg_nested_expansions 16.16 μs 16.02 μs -0.14 μs ⚪ Unchanged
full_peg_pipeline 4.20 μs 4.17 μs -0.02 μs ⚪ Unchanged
full_peg_simple 1.70 μs 1.68 μs -0.02 μs 🟢 -0.94%
function_call 3.39 μs 3.53 μs 0.14 μs 🟠 +4.16%
instantiate_shell 56.90 μs 56.90 μs 0.00 μs ⚪ Unchanged
instantiate_shell_with_init_scripts 24424.59 μs 24195.11 μs -229.48 μs ⚪ Unchanged
parse_peg_bash_completion 2210.35 μs 2203.05 μs -7.30 μs ⚪ Unchanged
parse_peg_complex 18.52 μs 18.56 μs 0.04 μs ⚪ Unchanged
parse_peg_for_loop 1.82 μs 1.80 μs -0.02 μs ⚪ Unchanged
parse_peg_pipeline 1.89 μs 1.94 μs 0.05 μs 🟠 +2.70%
parse_peg_simple 1.03 μs 1.00 μs -0.03 μs 🟢 -2.91%
run_echo_builtin_command 12.15 μs 12.18 μs 0.03 μs ⚪ Unchanged
tokenize_sample_script 3.40 μs 3.51 μs 0.11 μs 🟠 +3.39%

Code Coverage Report: Only Changed Files listed

Package Base Coverage New Coverage Difference
brush-core/src/commands.rs 🟢 91.59% 🟢 91.01% 🔴 -0.58%
brush-core/src/expansion.rs 🟢 96.17% 🟢 96.11% 🔴 -0.06%
brush-core/src/prompt.rs 🟢 94.48% 🟢 94.38% 🔴 -0.1%
brush-core/src/regex.rs 🟢 99.17% 🟢 99.12% 🔴 -0.05%
brush-core/src/results.rs 🟢 80.16% 🟢 83.33% 🟢 3.17%
brush-core/src/shell/parsing.rs 🟢 80% 🟢 77.14% 🔴 -2.86%
brush-core/src/shell/prompts.rs 🟢 97.62% 🟢 97.56% 🔴 -0.06%
brush-parser/src/arithmetic.rs 🟢 92.63% 🟢 92.55% 🔴 -0.08%
brush-parser/src/tokenizer.rs 🟢 93.56% 🟢 93.53% 🔴 -0.03%
brush-parser/src/word.rs 🟢 93.82% 🟢 93.76% 🔴 -0.06%
Overall Coverage 🟢 75.96% 🟢 75.95% 🔴 -0.01%

Minimum allowed coverage is 70%, this run produced 75.95%
Maximum allowed coverage difference is -5%, this run produced -0.01%

Test Summary: bash-completion test suite

Outcome Count Percentage
✅ Pass 1582 75.01
❗️ Error 17 0.81
❌ Fail 156 7.40
⏩ Skip 339 16.07
❎ Expected Fail 13 0.62
✔️ Unexpected Pass 2 0.09
📊 Total 2109 100.00

@DaZuo0122

Copy link
Copy Markdown
Author

@reubeno Absolutely, and yes, I’m happy to collaborate on this!

I agree that the fix should avoid meaningful behavioral changes on non-Windows platforms. I can rework it so the change is Windows-specific, likely gated behind #[cfg(windows)] or placed in the platform-specific layer.

Feel free to let me know what direction you prefer and I’ll update it.

@reubeno reubeno left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for being willing to iterate on this! I've got 2 main comments here.

It would also be ideal if you can find a way to test any non-trivial logic that gets added to the Windows code path.

let normalized_executable = if Path::new(executable_path).is_relative() {
let joined = context.shell.working_dir().join(executable_path);

let mut normalized = PathBuf::new();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

request(blocking): We already use the normalize_path crate elsewhere in this project. Could you look at using that instead of hand-normalizing?

.is_some_and(|f| f.is_terminal());

// On Windows, normalize relative executable paths before spawning.
#[cfg(windows)]

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

request(blocking): While we have some still sprinkled around, our strong preference is for platform-specific code to be invoked via the sys module in the crate. For non-Windows platforms, it should ideally be a no-op/pass-through that doesn't impact the code path; for Windows, it can perform this additional manipulation.

Don't hesitate to holler or ask questions if you could use additional pointers/examples on how best to do this.

@reubeno reubeno added state: updates requested Pull requests with updates requested platform: windows Issues that specifically relate to Windows platform targets labels Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform: windows Issues that specifically relate to Windows platform targets state: updates requested Pull requests with updates requested

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: [Windows] relative external command paths fail after cd because the executable path is not resolved against the shell working directory

2 participants