diff --git a/src/openhuman/runtime_python/process.rs b/src/openhuman/runtime_python/process.rs index d03f495a8..b669d716c 100644 --- a/src/openhuman/runtime_python/process.rs +++ b/src/openhuman/runtime_python/process.rs @@ -57,6 +57,10 @@ pub fn spawn_stdio_process( .stdout(Stdio::piped()) .stderr(Stdio::piped()) .kill_on_drop(true); + // Suppress the Windows conhost flash. This process is (re)spawned on every + // launch to run the runtime python server, so without CREATE_NO_WINDOW a + // CMD window blinks up each time the app starts (GH-4814). + crate::openhuman::inference::local::process_util::apply_no_window(&mut cmd); let child = cmd.spawn().with_context(|| { format!( @@ -76,3 +80,33 @@ pub fn spawn_stdio_process( Ok(child) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::openhuman::runtime_python::bootstrap::PythonSource; + + // `apply_no_window` is a no-op off Windows, but exercising the spawn path + // end-to-end keeps the GH-4814 CREATE_NO_WINDOW hook covered. `/bin/cat + // ` prints the file and exits, so it stands in for the python child. + #[cfg(unix)] + #[tokio::test] + async fn spawn_stdio_process_launches_child() { + let dir = tempfile::tempdir().expect("tempdir"); + let script = dir.path().join("payload.txt"); + std::fs::write(&script, b"ok").expect("write payload"); + + let resolved = ResolvedPython { + bin_dir: PathBuf::from("/bin"), + python_bin: PathBuf::from("/bin/cat"), + version: "test".to_string(), + source: PythonSource::System, + }; + let mut spec = PythonLaunchSpec::new(script); + spec.unbuffered = false; // `-u` is python-only; plain `cat ` here + + let mut child = spawn_stdio_process(&resolved, &spec).expect("spawn cat"); + let status = child.wait().await.expect("wait cat"); + assert!(status.success()); + } +} diff --git a/src/openhuman/runtime_python_server/kompress.rs b/src/openhuman/runtime_python_server/kompress.rs index 83927c3da..5f6dfead7 100644 --- a/src/openhuman/runtime_python_server/kompress.rs +++ b/src/openhuman/runtime_python_server/kompress.rs @@ -258,6 +258,9 @@ async fn run_step( cmd.env("HF_HOME", hf_home); cmd.env("HF_HUB_DISABLE_TELEMETRY", "1"); cmd.kill_on_drop(true); + // Re-provisioning can run mid-session if the venv is missing/corrupted, so + // suppress the Windows conhost flash here too (GH-4814). + crate::openhuman::inference::local::process_util::apply_no_window(&mut cmd); let output = match tokio::time::timeout(timeout, cmd.output()).await { Ok(Ok(o)) => o, @@ -314,4 +317,22 @@ mod tests { .to_string_lossy() .contains("answerdotai_ModernBERT-base")); } + + // Exercises the provisioning step path (including the GH-4814 + // CREATE_NO_WINDOW hook, a no-op off Windows) with a trivial binary so it + // stays covered without a real python toolchain. + #[cfg(unix)] + #[tokio::test] + async fn run_step_runs_a_trivial_binary() { + let hf = tempfile::tempdir().expect("tempdir"); + run_step( + Path::new("/bin/echo"), + &["ok"], + Duration::from_secs(30), + hf.path(), + "echo smoke", + ) + .await + .expect("echo step succeeds"); + } } diff --git a/src/openhuman/runtime_python_server/spacy.rs b/src/openhuman/runtime_python_server/spacy.rs index 1a90d82d9..4574760d4 100644 --- a/src/openhuman/runtime_python_server/spacy.rs +++ b/src/openhuman/runtime_python_server/spacy.rs @@ -181,6 +181,9 @@ async fn run_step(python_bin: &Path, args: &[&str], timeout: Duration, label: &s let mut cmd = Command::new(python_bin); cmd.args(args); cmd.kill_on_drop(true); + // spaCy venv provisioning can re-run mid-session if the venv is missing or + // its ready marker is stale, so suppress the Windows conhost flash (GH-4814). + crate::openhuman::inference::local::process_util::apply_no_window(&mut cmd); let output = match tokio::time::timeout(timeout, cmd.output()).await { Ok(Ok(output)) => output, @@ -397,4 +400,20 @@ mod tests { assert_eq!(response.entities[0].label, "PERSON"); assert_eq!(response.nouns, vec!["migration"]); } + + // Exercises the venv provisioning step path (including the GH-4814 + // CREATE_NO_WINDOW hook, a no-op off Windows) with a trivial binary so it + // stays covered without a real python toolchain. + #[cfg(unix)] + #[tokio::test] + async fn run_step_runs_a_trivial_binary() { + run_step( + Path::new("/bin/echo"), + &["ok"], + Duration::from_secs(30), + "echo smoke", + ) + .await + .expect("echo step succeeds"); + } }