fix(task): discover and run shebang file tasks on Windows#7941
Conversation
Summary of ChangesHello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Pull request overview
This PR fixes task discovery and execution for shebang-based file tasks on Windows. Previously, extensionless tasks with shebangs (e.g., #!/usr/bin/env bash) were not recognized as executable on Windows because the is_executable function only checked file extensions.
Changes:
- Modified
is_executableon Windows to also detect files with shebangs by checking for#!in the first 2 bytes - Updated task executor to parse shebangs and use the appropriate shell (e.g.,
bash.exe) instead of defaulting tocmd /c - Added e2e test to verify shebang task execution on Windows
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/file.rs | Added has_shebang function to detect shebang prefix and extracted has_known_executable_extension helper |
| src/task/task_executor.rs | Updated execution logic to differentiate between OS-executable files and shebang files, added shell_from_shebang parser |
| e2e-win/task.Tests.ps1 | Added test case for executing extensionless bash shebang tasks |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let can_exec_directly = if cfg!(windows) { | ||
| // On Windows, shebang-only files can't be executed directly by the OS. | ||
| // They need to be run through a shell (e.g. bash.exe). | ||
| #[cfg(windows)] | ||
| { | ||
| crate::file::has_known_executable_extension(file) | ||
| } | ||
| #[cfg(not(windows))] | ||
| { | ||
| is_executable(file) | ||
| } | ||
| } else { | ||
| is_executable(file) | ||
| }; |
There was a problem hiding this comment.
The conditional compilation attributes inside the if cfg!(windows) block are redundant. The runtime check already determines the platform, making the compile-time #[cfg(...)] attributes unnecessary and potentially confusing. Simplify by removing the inner #[cfg] blocks and using only the runtime if cfg!(windows) check.
| shell | ||
| }; | ||
| let args: Vec<String> = parts.map(|s| s.to_string()).collect(); | ||
| Some(once(shell.to_string()).chain(args).collect()) |
There was a problem hiding this comment.
The once function is used but not imported at the top of the file. Add use std::iter::once; to the imports section for clarity.
| @" | ||
| #!/usr/bin/env bash | ||
| echo "from-bash" | ||
| "@ | Out-File -FilePath "tasks\shebangtask" -Encoding utf8NoBOM -NoNewline |
There was a problem hiding this comment.
The -NoNewline flag will strip the trailing newline from the here-string, but the shebang line itself (line 59) should end with a newline character for proper parsing. Remove -NoNewline to ensure the file has correct line endings.
| "@ | Out-File -FilePath "tasks\shebangtask" -Encoding utf8NoBOM -NoNewline | |
| "@ | Out-File -FilePath "tasks\shebangtask" -Encoding utf8NoBOM |
On Windows, file-based tasks without a recognized extension (e.g., .bat, .ps1) were filtered out during task discovery because `is_executable` only checked file extensions. This meant extensionless bash scripts in task directories were invisible to mise on Windows. Now, `is_executable` on Windows also checks for a shebang (#!) in the first 2 bytes of the file. During execution, the shebang is parsed to determine the correct shell (e.g., bash.exe for #!/usr/bin/env bash) instead of falling back to `cmd /c` which can't run bash scripts. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
cd0f6f2 to
1f8bd9d
Compare
There was a problem hiding this comment.
Code Review
This pull request adds support for discovering and running shebang file tasks on Windows, which is a great improvement. The changes correctly modify is_executable to detect shebangs and update the task execution logic to use an interpreter. The new e2e test is also a good addition. I have one suggestion to improve the shebang parsing logic to handle quoted arguments correctly.
| use std::io::{BufRead, BufReader}; | ||
| let f = std::fs::File::open(path).ok()?; | ||
| let mut reader = BufReader::new(f); | ||
| let mut first_line = String::new(); | ||
| reader.read_line(&mut first_line).ok()?; | ||
| let shebang = first_line.strip_prefix("#!")?; | ||
| let shebang = shebang.strip_prefix("/usr/bin/env -S").unwrap_or(shebang); | ||
| let shebang = shebang.strip_prefix("/usr/bin/env").unwrap_or(shebang); | ||
| let mut parts = shebang.split_whitespace(); | ||
| let shell = parts.next()?; |
There was a problem hiding this comment.
The use of split_whitespace() for parsing the shebang can lead to incorrect behavior when arguments are quoted. For instance, a shebang like #!/usr/bin/env bash -c "echo hello" would be parsed incorrectly. It's better to use shell_words::split, which is already a dependency in this project and correctly handles quoted arguments.
let mut parts = shell_words::split(shebang.trim()).ok()?;
if parts.is_empty() {
return None;
}
let shell = parts.remove(0);
// On Windows, convert unix paths like /bin/bash to just the binary name
let shell = if cfg!(windows) {
shell.rsplit('/').next().unwrap_or(&shell).to_string()
} else {
shell
};
let args: Vec<String> = parts;
Some(once(shell).chain(args).collect())
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.1.12 x -- echo |
20.0 ± 0.4 | 19.2 | 23.1 | 1.00 |
mise x -- echo |
20.3 ± 0.7 | 19.3 | 24.4 | 1.02 ± 0.04 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.1.12 env |
19.4 ± 0.5 | 18.6 | 24.2 | 1.00 |
mise env |
19.9 ± 0.5 | 18.8 | 21.7 | 1.03 ± 0.04 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.1.12 hook-env |
20.0 ± 0.3 | 19.3 | 21.2 | 1.00 |
mise hook-env |
20.5 ± 2.0 | 19.4 | 63.9 | 1.02 ± 0.10 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.1.12 ls |
18.0 ± 0.4 | 17.2 | 19.3 | 1.00 |
mise ls |
18.2 ± 0.5 | 17.4 | 20.5 | 1.01 ± 0.03 |
xtasks/test/perf
| Command | mise-2026.1.12 | mise | Variance |
|---|---|---|---|
| install (cached) | 111ms | 111ms | +0% |
| ls (cached) | 70ms | 70ms | +0% |
| bin-paths (cached) | 74ms | 74ms | +0% |
| task-ls (cached) | 532ms | 538ms | -1% |
## Summary - On Windows, extensionless file tasks (e.g., `tasks/build` with `#!/usr/bin/env bash`) were invisible to mise because `is_executable` only checked file extensions - Now `is_executable` on Windows also checks for a shebang (`#!`) in the first 2 bytes, so these tasks are discovered - During execution, the shebang is parsed to determine the correct shell (e.g., `bash.exe` for `#!/usr/bin/env bash`) instead of falling back to `cmd /c` - Unix paths in shebangs like `/bin/bash` are converted to just the binary name on Windows ## Test plan - [ ] Verify existing Windows e2e task tests still pass - [ ] Verify new `executes a shebang task with bash` e2e test passes on Windows CI - [ ] Verify Linux/macOS behavior is unchanged (no code paths affected on non-Windows) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it changes Windows task discovery/executability checks and alters how file tasks choose their execution shell, which could affect existing task behavior and command invocation. > > **Overview** > Enables Windows to **discover and run extensionless file tasks with a `#!` shebang**, treating them as executable for task discovery. > > Updates task execution to **avoid direct execution for shebang-only files** and instead pick a shell from the file’s shebang (with Windows-friendly path normalization) or from the file extension (notably `.ps1` via `pwsh -File`) before falling back to the default file shell. > > Adds a Windows e2e test covering a no-extension bash-shebang task. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1f8bd9d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Summary
tasks/buildwith#!/usr/bin/env bash) were invisible to mise becauseis_executableonly checked file extensionsis_executableon Windows also checks for a shebang (#!) in the first 2 bytes, so these tasks are discoveredbash.exefor#!/usr/bin/env bash) instead of falling back tocmd /c/bin/bashare converted to just the binary name on WindowsTest plan
executes a shebang task with bashe2e test passes on Windows CI🤖 Generated with Claude Code
Note
Medium Risk
Medium risk because it changes Windows task discovery/executability checks and alters how file tasks choose their execution shell, which could affect existing task behavior and command invocation.
Overview
Enables Windows to discover and run extensionless file tasks with a
#!shebang, treating them as executable for task discovery.Updates task execution to avoid direct execution for shebang-only files and instead pick a shell from the file’s shebang (with Windows-friendly path normalization) or from the file extension (notably
.ps1viapwsh -File) before falling back to the default file shell.Adds a Windows e2e test covering a no-extension bash-shebang task.
Written by Cursor Bugbot for commit 1f8bd9d. This will update automatically on new commits. Configure here.