diff --git a/AGENTS.md b/AGENTS.md
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -65,6 +65,7 @@ Parity is measured, not guessed: `crates/engine/catalog/menus.txt` lists the inc
 
 ## Quality gates (before every commit)
 `cargo xtask ci` = fmt check, `clippy --workspace --all-targets -D warnings`, tests, assets, layers, wasm check. Commit after each arc of work that builds; one topic per commit.
+Its test step sets `SOUNDCRAFT_NO_SYSTEM_PLUGINS=1` so tests never load the machine's installed VST3/CLAP plugins (some never let a process exit once loaded). Set it yourself when running `cargo test` directly on a machine with plugins installed.
 
 ## Release
 Push to the `release` branch to build signed installers for macOS (universal DMG), Windows (x64/x86 MSI + zip), Linux (AppImage, deb, rpm, tar.gz, Flatpak; riscv64 tar.gz), FreeBSD and Web (wasm zip). Version: `cargo xtask version [set X.Y.Z]`. See `docs/release-playbook.md` and craftrules `release/playbook.md`.
diff --git a/crates/clap-host/src/scan.rs b/crates/clap-host/src/scan.rs
--- a/crates/clap-host/src/scan.rs
+++ b/crates/clap-host/src/scan.rs
@@ -11,14 +11,20 @@ const MAX_BUNDLES: usize = 4096;
 const MAX_ENTRIES: usize = 100_000;
 
 /// The standard CLAP search locations for this OS (existing or not), followed by every entry
-/// of the `CLAP_PATH` environment variable.
+/// of the `CLAP_PATH` environment variable. With `SOUNDCRAFT_NO_SYSTEM_PLUGINS` set (test runs:
+/// `cargo xtask ci` sets it), only `CLAP_PATH`, so tests never load the machine's plugins.
 pub fn default_search_paths() -> Vec<PathBuf> {
-    let mut v = os_search_paths(
-        std::env::var_os("HOME").map(PathBuf::from),
-        std::env::var_os("COMMONPROGRAMFILES").map(PathBuf::from),
-        std::env::var_os("LOCALAPPDATA").map(PathBuf::from),
-    );
-    if let Some(cp) = std::env::var_os("CLAP_PATH") {
+    search_paths(|k| std::env::var_os(k))
+}
+
+/// [`default_search_paths`] with the environment given.
+fn search_paths(env: impl Fn(&str) -> Option<std::ffi::OsString>) -> Vec<PathBuf> {
+    let mut v = if env("SOUNDCRAFT_NO_SYSTEM_PLUGINS").is_some_and(|v| !v.is_empty()) {
+        Vec::new()
+    } else {
+        os_search_paths(env("HOME").map(PathBuf::from), env("COMMONPROGRAMFILES").map(PathBuf::from), env("LOCALAPPDATA").map(PathBuf::from))
+    };
+    if let Some(cp) = env("CLAP_PATH") {
         v.extend(std::env::split_paths(&cp).filter(|p| !p.as_os_str().is_empty()));
     }
     v
@@ -195,7 +201,34 @@ mod tests {
         } else {
             assert_eq!(v, vec![PathBuf::from("/home/u/.clap"), PathBuf::from("/usr/lib/clap"), PathBuf::from("/usr/local/lib/clap")]);
         }
-        assert!(default_search_paths().len() >= v.len().saturating_sub(1));
+        let env = |k: &str| match k {
+            "HOME" => Some("/home/u".into()),
+            "COMMONPROGRAMFILES" => Some("C:/CPF".into()),
+            "LOCALAPPDATA" => Some("C:/LAD".into()),
+            _ => None,
+        };
+        assert_eq!(search_paths(env), v);
+    }
+
+    #[test]
+    fn no_system_plugins_keeps_only_clap_path() {
+        let extra = std::env::join_paths(["/opt/clap-a", "/opt/clap-b"]).unwrap();
+        let env = |k: &str| match k {
+            "SOUNDCRAFT_NO_SYSTEM_PLUGINS" => Some("1".into()),
+            "HOME" => Some("/home/u".into()),
+            "CLAP_PATH" => Some(extra.clone()),
+            _ => None,
+        };
+        assert_eq!(search_paths(env), vec![PathBuf::from("/opt/clap-a"), PathBuf::from("/opt/clap-b")]);
+        // Set but empty counts as unset.
+        let env = |k: &str| match k {
+            "SOUNDCRAFT_NO_SYSTEM_PLUGINS" => Some("".into()),
+            "HOME" => Some("/home/u".into()),
+            "COMMONPROGRAMFILES" => Some("C:/CPF".into()),
+            "LOCALAPPDATA" => Some("C:/LAD".into()),
+            _ => None,
+        };
+        assert_eq!(search_paths(env), os_search_paths(Some("/home/u".into()), Some("C:/CPF".into()), Some("C:/LAD".into())));
     }
 
     #[test]
diff --git a/crates/engine/src/io.rs b/crates/engine/src/io.rs
--- a/crates/engine/src/io.rs
+++ b/crates/engine/src/io.rs
@@ -1710,7 +1710,8 @@ mod save_tests {
         let mut r = Engine::default();
         assert_eq!(
             open_session(&mut r, &path("B/Moved.scraft")).unwrap(),
-            vec![dir.join("A").join("Audio Files/Audio 1_02.wav").display().to_string()]
+            // Joined a component at a time: the engine reports the platform's own separators.
+            vec![dir.join("A").join("Audio Files").join("Audio 1_02.wav").display().to_string()]
         );
         let _ = std::fs::remove_dir_all(&dir);
     }
diff --git a/crates/vst3-host/src/scan.rs b/crates/vst3-host/src/scan.rs
--- a/crates/vst3-host/src/scan.rs
+++ b/crates/vst3-host/src/scan.rs
@@ -34,15 +34,21 @@ impl Os {
 }
 
 /// The standard VST3 search locations for this OS (existing or not), followed by every entry of
-/// the `VST3_PATH` environment variable.
+/// the `VST3_PATH` environment variable. With `SOUNDCRAFT_NO_SYSTEM_PLUGINS` set (test runs:
+/// `cargo xtask ci` sets it), only `VST3_PATH`: tests must not load the machine's installed
+/// plugins, some of which never let the process exit once loaded.
 pub fn default_search_paths() -> Vec<PathBuf> {
-    let mut v = os_search_paths(
-        Os::current(),
-        std::env::var_os("HOME").map(PathBuf::from),
-        std::env::var_os("COMMONPROGRAMFILES").map(PathBuf::from),
-        std::env::var_os("LOCALAPPDATA").map(PathBuf::from),
-    );
-    if let Some(cp) = std::env::var_os("VST3_PATH") {
+    search_paths(Os::current(), |k| std::env::var_os(k))
+}
+
+/// [`default_search_paths`] for `os` with the environment given.
+fn search_paths(os: Os, env: impl Fn(&str) -> Option<std::ffi::OsString>) -> Vec<PathBuf> {
+    let mut v = if env("SOUNDCRAFT_NO_SYSTEM_PLUGINS").is_some_and(|v| !v.is_empty()) {
+        Vec::new()
+    } else {
+        os_search_paths(os, env("HOME").map(PathBuf::from), env("COMMONPROGRAMFILES").map(PathBuf::from), env("LOCALAPPDATA").map(PathBuf::from))
+    };
+    if let Some(cp) = env("VST3_PATH") {
         v.extend(std::env::split_paths(&cp).filter(|p| !p.as_os_str().is_empty()));
     }
     v
@@ -300,7 +306,27 @@ mod tests {
         assert_eq!(win.len(), 2);
         let unix = os_search_paths(Os::Unix, Some("/home/u".into()), None, None);
         assert_eq!(unix, vec![PathBuf::from("/home/u/.vst3"), PathBuf::from("/usr/lib/vst3"), PathBuf::from("/usr/local/lib/vst3")]);
-        assert!(!default_search_paths().is_empty());
+        let env = |k: &str| (k == "HOME").then(|| "/home/u".into());
+        assert_eq!(search_paths(Os::Unix, env), unix);
+    }
+
+    #[test]
+    fn no_system_plugins_keeps_only_vst3_path() {
+        let extra = std::env::join_paths(["/opt/vst-a", "/opt/vst-b"]).unwrap();
+        let env = |k: &str| match k {
+            "SOUNDCRAFT_NO_SYSTEM_PLUGINS" => Some("1".into()),
+            "HOME" => Some("/home/u".into()),
+            "VST3_PATH" => Some(extra.clone()),
+            _ => None,
+        };
+        assert_eq!(search_paths(Os::Unix, env), vec![PathBuf::from("/opt/vst-a"), PathBuf::from("/opt/vst-b")]);
+        // Set but empty counts as unset.
+        let env = |k: &str| match k {
+            "SOUNDCRAFT_NO_SYSTEM_PLUGINS" => Some("".into()),
+            "HOME" => Some("/home/u".into()),
+            _ => None,
+        };
+        assert_eq!(search_paths(Os::Unix, env).len(), 3);
     }
 
     #[test]
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -11,8 +11,16 @@ fn root() -> PathBuf {
 }
 
 fn cargo(args: &[&str]) -> Result<(), String> {
-    let st =
-        Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".into())).args(args).current_dir(root()).status().map_err(|e| e.to_string())?;
+    cargo_with_env(args, &[])
+}
+
+fn cargo_with_env(args: &[&str], env: &[(&str, &str)]) -> Result<(), String> {
+    let st = Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
+        .args(args)
+        .envs(env.iter().copied())
+        .current_dir(root())
+        .status()
+        .map_err(|e| e.to_string())?;
     if st.success() { Ok(()) } else { Err(format!("cargo {} failed", args.join(" "))) }
 }
 
@@ -148,7 +156,9 @@ fn ci() -> Result<(), String> {
     let steps: Vec<(&str, Box<dyn Fn() -> Result<(), String>>)> = vec![
         ("fmt", Box::new(|| cargo(&["fmt", "--all", "--", "--check"]))),
         ("clippy", Box::new(|| cargo(&["clippy", "--workspace", "--all-targets", "--", "-D", "warnings"]))),
-        ("test", Box::new(|| cargo(&["test", "--workspace"]))),
+        // Tests never load the machine's installed plugins: some never let a process exit once
+        // loaded, which hangs the run (seen on Windows with large plugin collections).
+        ("test", Box::new(|| cargo_with_env(&["test", "--workspace"], &[("SOUNDCRAFT_NO_SYSTEM_PLUGINS", "1")]))),
         ("assets", Box::new(assets)),
         ("layers", Box::new(layers)),
         ("wasm", Box::new(wasm)),
