diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 74f9258..3b39e1a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,10 +8,6 @@ on:
     branches:
     - main
 
-defaults:
-  run:
-    shell: bash
-
 jobs:
   all:
     name: All
@@ -49,4 +45,46 @@ jobs:
       run: cargo run -- --help
 
     - name: Test upt list
+      run: cargo run -- list
+
+  msys2:
+    name: MSYS2
+
+    runs-on: windows-latest
+
+    env:
+      RUSTFLAGS: --deny warnings
+
+    steps:
+    - uses: actions/checkout@v4
+
+    - name: Install Rust Toolchain Components
+      uses: dtolnay/rust-toolchain@stable
+
+    - uses: Swatinem/rust-cache@v2
+
+    - name: Test
+      run: cargo test --all
+
+    - name: Clippy
+      run: cargo clippy --all --all-targets -- -D warnings
+
+    - name: Format
+      run: cargo fmt --all --check
+
+    - uses: msys2/setup-msys2@v2
+      with:
+        path-type: inherit
+
+    - name: Test upt --help on Windows Bash
+      if: runner.os == 'Windows'
+      run: cargo run -- --help
+      shell: bash
+
+    - name: Test upt --help on MSYS2
+      shell: msys2 {0}
+      run: cargo run -- --help
+
+    - name: Test upt list
+      shell: msys2 {0}
       run: cargo run -- list
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 8bcc9a5..e191295 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,5 +8,5 @@ mod utils;
 mod vendor;
 
 pub use error::UptError;
-pub use utils::detect_os;
+pub use utils::{detect_os, run_command};
 pub use vendor::{detect_vendor, init_vendor, Vendor};
diff --git a/src/macros.rs b/src/macros.rs
index cb912c2..184d9d3 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -65,6 +65,7 @@ macro_rules! os_vendors {
                 $(
                     $os => vec![$($tool),+].into_iter().filter_map(|tool| which_cmd(tool).map(|bin_name| (tool, bin_name))).collect(),
                 )+
+                "windows/msys2" => vec![("pacman","pacman")],
                 _ => ["apt", "dnf", "pacman"].into_iter().map(|tool| (tool, tool)).collect(),
             };
             match $crate::utils::find_tool(&pairs) {
diff --git a/src/main.rs b/src/main.rs
index 955b8a9..3798c33 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,11 +1,12 @@
-use std::env;
 use std::path::Path;
-use std::process::{self, Command};
-use upt::{detect_os, detect_vendor, init_vendor, UptError, Vendor};
+use std::{env, process};
+use upt::{detect_os, detect_vendor, init_vendor, run_command, UptError, Vendor};
 
 fn main() {
     match run() {
-        Ok(v) => v,
+        Ok(c) => {
+            process::exit(c);
+        }
         Err(e) => {
             eprintln!("{}", e);
             process::exit(1);
@@ -13,7 +14,7 @@ fn main() {
     }
 }
 
-fn run() -> Result<(), Box<dyn std::error::Error>> {
+fn run() -> Result<i32, Box<dyn std::error::Error>> {
     let env_args = env::args().collect::<Vec<String>>();
     let bin = Path::new(&env_args[0])
         .file_stem()
@@ -28,17 +29,17 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
         Ok(v) => v,
         Err(UptError::DisplayHelp(t)) => {
             println!("{t}");
-            return Ok(());
+            return Ok(0);
         }
         Err(e) => return Err(e.into()),
     };
     if let Ok(v) = std::env::var("UPT_DRY_RUN") {
         if v == "true" || v == "1" {
             println!("{}", cmd);
-            return Ok(());
+            return Ok(0);
         }
     }
-    run_cmd(cmd.as_str(), &os)
+    run_command(cmd.as_str(), &os)
 }
 
 fn create_cmd(vendor: &Vendor, args: &[String], os: &str) -> Result<String, UptError> {
@@ -50,21 +51,3 @@ fn create_cmd(vendor: &Vendor, args: &[String], os: &str) -> Result<String, UptE
     let cmd = tool.eval(&task)?;
     Ok(cmd)
 }
-
-#[cfg(not(target_os = "windows"))]
-fn run_cmd(cmd: &str, _os: &str) -> Result<(), Box<dyn std::error::Error>> {
-    let mut child = Command::new("sh").arg("-c").arg(cmd).spawn()?;
-    child.wait()?;
-    Ok(())
-}
-
-#[cfg(target_os = "windows")]
-fn run_cmd(cmd: &str, os: &str) -> Result<(), Box<dyn std::error::Error>> {
-    let mut child = if os == "windows/msys2" {
-        Command::new("sh").arg("-c").arg(cmd).spawn()?
-    } else {
-        Command::new("cmd").args(["/C", cmd]).spawn()?
-    };
-    child.wait()?;
-    Ok(())
-}
diff --git a/src/utils.rs b/src/utils.rs
index 5ca06e9..e5fdced 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,3 +1,5 @@
+use std::process::Command;
+
 use which::which;
 
 pub fn find_tool(pairs: &[(&str, &str)]) -> Option<String> {
@@ -38,11 +40,15 @@ pub fn find_tool(pairs: &[(&str, &str)]) -> Option<String> {
 
 #[cfg(target_os = "windows")]
 pub fn detect_os() -> Option<String> {
-    if let Ok(true) = std::env::var("OSTYPE").map(|v| v == "msys") {
-        Some("windows/msys2".to_string())
-    } else {
-        Some("windows".to_string())
+    if std::env::var("MSYSTEM").is_ok() {
+        let os = "windows/msys2";
+        if let Ok(output) = Command::new("sh").arg("-c").arg("which pacman").output() {
+            if output.status.success() {
+                return Some(os.to_string());
+            }
+        }
     }
+    Some("windows".to_string())
 }
 
 #[cfg(target_os = "macos")]
@@ -72,3 +78,12 @@ pub fn detect_os() -> Option<String> {
     let id = id[3..].trim_matches('"');
     Some(id.to_string())
 }
+
+pub fn run_command(cmd: &str, os: &str) -> Result<i32, Box<dyn std::error::Error>> {
+    let exit_status = if os == "windows" {
+        Command::new("cmd").args(["/C", cmd]).status()?
+    } else {
+        Command::new("sh").arg("-c").arg(cmd).status()?
+    };
+    Ok(exit_status.code().unwrap_or_default())
+}
diff --git a/src/vendor.rs b/src/vendor.rs
index 05444b9..2daa9ff 100644
--- a/src/vendor.rs
+++ b/src/vendor.rs
@@ -41,7 +41,6 @@ os_vendors!(
   "garuda" => "pacman";
   "antergos" => "pacman";
   "kaos" => "pacman";
-  "windows/msys2" => "pacman";
   // apk
   "alpine" => "apk";
   "postmarket" => "apk";