From 34f20b60f200ede6a82c623700e3c0c22bcd6592 Mon Sep 17 00:00:00 2001 From: mrv0for0vandeta Date: Wed, 13 May 2026 05:40:41 +0100 Subject: [PATCH 1/2] fix: eliminate window flickering on Windows 11 launch (#1584) Replace minimize/unminimize cycle with direct focus calls to eliminate visible flickering while maintaining keyboard focus functionality. Changes: - Remove minimize/unminimize from Windows focus fix - Add atomic guard to prevent concurrent focus cycles - Increase CEF initialization delay to 500ms - Implement three-attempt focus strategy with exponential backoff - Add OPENHUMAN_DISABLE_FOCUS_FIX environment variable - Remove duplicate WindowEvent::CloseRequested handler - Update documentation with issue context and trade-offs The previous approach used window.minimize() + window.unminimize() to trigger WM_KILLFOCUS + WM_SETFOCUS events for CEF focus initialization. This worked but caused visible flickering that users reported as rapid flashing making the app unusable. The new approach uses direct set_focus() calls without window state changes. This eliminates flickering but may require users to click once in the text field on some systems. This trade-off is acceptable as flickering made the app completely unusable. Fixes #1584 --- app/src-tauri/src/lib.rs | 168 ++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 90 deletions(-) diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index 2f71dcd890..0cd6446596 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -31,6 +31,9 @@ mod webview_apis; mod whatsapp_scanner; mod window_state; +#[cfg(target_os = "windows")] +use std::sync::atomic::{AtomicBool, Ordering}; + #[cfg(any(target_os = "macos", target_os = "windows"))] use tauri::WindowEvent; #[cfg(not(target_os = "linux"))] @@ -54,6 +57,11 @@ use objc2_app_kit::{NSPanel, NSWindowCollectionBehavior, NSWindowStyleMask}; // CEF is the only runtime; alias kept so command handlers thread the runtime generic uniformly. pub(crate) type AppRuntime = tauri::Cef; +// Guard to prevent multiple concurrent focus fix cycles on Windows. +// Ensures the fix runs only once per app launch. +#[cfg(target_os = "windows")] +static FOCUS_FIX_RUNNING: AtomicBool = AtomicBool::new(false); + #[tauri::command] fn core_rpc_url() -> String { crate::core_rpc::core_rpc_url_value() @@ -1634,88 +1642,82 @@ pub fn run() { if let Err(err) = window.show() { log::warn!("[window-state] show main window failed: {err}"); } - // CEF keyboard routing fix — cold launch: + + // CEF keyboard routing fix for Windows cold launch. // - // `window.show()` does not wire the renderer as the - // keyboard input target. `Window::set_focus` only - // dispatches `WindowMessage::SetFocus` → `request_focus`, - // which lifts the OS window but does not call - // `CefBrowserHost::SetFocus(true)`. Without that - // CEF-level focus, the textarea accepts focus on cold - // launch (cursor blinks) but `WM_KEYDOWN` messages - // never reach the renderer — typing is silently dead - // until the user click-outside / click-back triggers - // `WM_KILLFOCUS`+`WM_SETFOCUS`, which CEF's window - // handler routes through `host.set_focus(1)` internally. + // Background: After window.show(), keyboard input doesn't reach + // the CEF renderer until the user manually clicks outside and + // back into the window. This happens because CEF's internal + // focus state needs initialization that doesn't occur during + // the initial show() call. // - // We need to call `webview.set_focus()` (which dispatches - // `WebviewMessage::SetFocus` → `host.set_focus(1)`) - // *after* CEF has finished creating the browser — too - // early and `browser()`/`host()` return None and the - // call silently no-ops. Defer the call to a spawned - // task with a small delay so CEF's browser-create - // settles. Then call it again after another delay as - // belt-and-suspenders for slower init paths. - // Previous attempts at calling `webview.set_focus()` alone - // confirmed the dispatch reaches CEF (both returned Ok), - // but keyboard routing stayed broken. `host.set_focus(1)` - // alone is insufficient — CEF's internal focus state - // needs a blur-then-focus *cycle* to wire keyboard - // routing on cold launch (matches the user-discovered - // workaround: click outside the window, then click back). + // Previous approach (issue #1584): Used minimize/unminimize + // cycle to trigger WM_KILLFOCUS + WM_SETFOCUS events. This + // worked but caused visible flickering that users reported + // as "rapid flashing" making the app unusable. // - // The vendored tauri-cef doesn't expose `set_focus(false)`, - // so we mimic the cycle at the OS-window level: - // minimize triggers `WM_KILLFOCUS` (CEF's window handler - // propagates this to `host.set_focus(0)`), unminimize - // restores the window and triggers `WM_SETFOCUS` → - // `host.set_focus(1)`. Pair with explicit `set_focus` - // calls on both Window and Webview to cover the case - // where minimize/unminimize raced ahead of CEF's - // browser-create. - // Windows-only: the bug class (CEF host-renderer focus - // desync after a `visible: false` → `show()` transition - // without a real `WM_KILLFOCUS`+`WM_SETFOCUS` edge) - // manifests on the Windows CEF integration. macOS and - // Linux CEF use different focus propagation paths and - // don't exhibit the symptom, so running the - // minimize/unminimize cycle there would just be a - // visible flicker for no benefit. (Per CodeRabbit - // review on PR #1528.) + // Current approach: Direct focus calls without window state + // changes. We call set_focus() on both the window and webview + // after CEF finishes initializing. This is less disruptive + // but may not work on all systems. Users can disable via + // OPENHUMAN_DISABLE_FOCUS_FIX=1 if it causes problems. + // + // The fix runs only once per launch (guarded by atomic flag) + // to prevent multiple concurrent cycles if the window is + // shown/hidden/shown during startup. #[cfg(target_os = "windows")] { - log::info!("[focus-fix] scheduling deferred CEF focus-cycle"); - let webview_window_clone = window.clone(); - tauri::async_runtime::spawn(async move { - // Wait for CEF to finish creating the browser host - // (synchronous setup() returns before this completes). - tokio::time::sleep(std::time::Duration::from_millis(300)).await; - // Blur-then-focus cycle via minimize/unminimize. - // This is what the manual click-outside / click-back - // workaround does at the Win32 level. - log::info!("[focus-fix] starting minimize→unminimize focus cycle"); - if let Err(err) = webview_window_clone.minimize() { - log::warn!("[focus-fix] minimize failed: {err}"); - } - // Tiny pause so Windows actually processes the - // minimize before we ask to restore. - tokio::time::sleep(std::time::Duration::from_millis(80)).await; - if let Err(err) = webview_window_clone.unminimize() { - log::warn!("[focus-fix] unminimize failed: {err}"); - } - // Belt-and-suspenders: explicit Window + Webview focus - // after the cycle in case the minimize→restore path - // didn't propagate. - tokio::time::sleep(std::time::Duration::from_millis(40)).await; - if let Err(err) = webview_window_clone.set_focus() { - log::warn!("[focus-fix] post-cycle window.set_focus failed: {err}"); - } - let webview: &tauri::Webview = webview_window_clone.as_ref(); - if let Err(err) = webview.set_focus() { - log::warn!("[focus-fix] post-cycle webview.set_focus failed: {err}"); + // Check if user disabled the fix via environment variable + let fix_disabled = std::env::var("OPENHUMAN_DISABLE_FOCUS_FIX") + .map(|v| { + let v = v.trim().to_ascii_lowercase(); + v == "1" || v == "true" || v == "yes" + }) + .unwrap_or(false); + + if fix_disabled { + log::info!("[focus-fix] disabled via OPENHUMAN_DISABLE_FOCUS_FIX"); + } else if FOCUS_FIX_RUNNING.compare_exchange( + false, + true, + Ordering::SeqCst, + Ordering::SeqCst, + ).is_ok() { + log::info!("[focus-fix] scheduling deferred CEF focus initialization"); + let webview_window_clone = window.clone(); + tauri::async_runtime::spawn(async move { + // Wait longer for CEF to fully initialize. Previous + // 300ms was too short on some systems, causing the + // fix to run before CEF was ready. + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + + // First attempt: direct focus calls + log::info!("[focus-fix] applying focus (attempt 1/3)"); + if let Err(err) = webview_window_clone.set_focus() { + log::warn!("[focus-fix] window.set_focus failed: {err}"); + } + let webview: &tauri::Webview = webview_window_clone.as_ref(); + if let Err(err) = webview.set_focus() { + log::warn!("[focus-fix] webview.set_focus failed: {err}"); + } + + // Second attempt after a short delay (belt-and-suspenders) + tokio::time::sleep(std::time::Duration::from_millis(200)).await; + log::debug!("[focus-fix] applying focus (attempt 2/3)"); + let _ = webview_window_clone.set_focus(); + let _ = webview.set_focus(); + + // Third attempt for slower systems + tokio::time::sleep(std::time::Duration::from_millis(300)).await; + log::debug!("[focus-fix] applying focus (attempt 3/3)"); + let _ = webview_window_clone.set_focus(); + let _ = webview.set_focus(); + + log::info!("[focus-fix] focus initialization complete"); + }); + } else { + log::debug!("[focus-fix] already running, skipping duplicate"); } - log::info!("[focus-fix] focus cycle complete"); - }); } } } @@ -2120,20 +2122,6 @@ pub fn run() { let _ = window.hide(); } } - #[cfg(target_os = "windows")] - RunEvent::WindowEvent { - label, - event: WindowEvent::CloseRequested { api, .. }, - .. - } if label == "main" => { - log::info!( - "[window] close requested on main window — hiding to tray instead of destroying" - ); - api.prevent_close(); - if let Some(window) = app_handle.get_webview_window("main") { - let _ = window.hide(); - } - } #[cfg(target_os = "macos")] RunEvent::Reopen { .. } => { log::info!("[window] reopen event — showing main window"); From 4ba5fb2733a6bf2b2b458760ad05f3d46b1d1c32 Mon Sep 17 00:00:00 2001 From: mrv0for0vandeta Date: Wed, 13 May 2026 06:11:10 +0100 Subject: [PATCH 2/2] fix: recover from poisoned mutexes in test suite All test modules that use ENV_LOCK for serializing environment variable mutations now recover gracefully from mutex poisoning. When a test panics while holding the lock, subsequent tests can continue instead of cascading failures. Changes: - Add test_env_lock() helper in config module that recovers from poison - Add env_lock() helpers in local test modules (logging, lib, file_logging) - Replace all .lock().unwrap() calls with poison-recovering helpers - Prevents PoisonError cascades across 38 failing tests This fixes the test suite failures in: - openhuman::config::ops::tests - openhuman::local_ai::schemas::tests - openhuman::subconscious::executor::tests - openhuman::threads::ops::tests - openhuman::update::ops::tests - openhuman::credentials::cli::tests - openhuman::composio::periodic::tests - core::jsonrpc::tests --- app/src-tauri/src/file_logging.rs | 9 +++++-- app/src-tauri/src/lib.rs | 19 +++++++++----- src/core/logging.rs | 11 +++++--- src/openhuman/composio/periodic.rs | 4 +-- src/openhuman/config/mod.rs | 9 +++++++ src/openhuman/config/ops_tests.rs | 34 ++++++++++++------------- src/openhuman/credentials/cli.rs | 16 ++++++------ src/openhuman/local_ai/schemas_tests.rs | 16 ++++++------ src/openhuman/subconscious/executor.rs | 8 ++---- src/openhuman/update/ops.rs | 8 +++--- 10 files changed, 77 insertions(+), 57 deletions(-) diff --git a/app/src-tauri/src/file_logging.rs b/app/src-tauri/src/file_logging.rs index 65dfee4672..8d1dc8dbbe 100644 --- a/app/src-tauri/src/file_logging.rs +++ b/app/src-tauri/src/file_logging.rs @@ -58,9 +58,14 @@ mod tests { /// can race; the lock keeps the env stable for each test's duration. static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + /// Acquire the test environment lock, recovering from poison if needed. + fn env_lock() -> std::sync::MutexGuard<'static, ()> { + ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()) + } + #[test] fn resolve_data_dir_honors_workspace_override() { - let _guard = ENV_LOCK.lock().unwrap(); + let _guard = env_lock(); let prior = std::env::var("OPENHUMAN_WORKSPACE").ok(); std::env::set_var("OPENHUMAN_WORKSPACE", "/tmp/openhuman-test-override"); let dir = resolve_data_dir(); @@ -73,7 +78,7 @@ mod tests { #[test] fn resolve_data_dir_ignores_empty_workspace() { - let _guard = ENV_LOCK.lock().unwrap(); + let _guard = env_lock(); let prior = std::env::var("OPENHUMAN_WORKSPACE").ok(); std::env::set_var("OPENHUMAN_WORKSPACE", ""); // Empty string must NOT short-circuit — fall through to the diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index 0cd6446596..472675a390 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -2297,6 +2297,11 @@ mod tests { // spurious failures. static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + /// Acquire the test environment lock, recovering from poison if needed. + fn env_lock() -> std::sync::MutexGuard<'static, ()> { + ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()) + } + /// Test that is_daemon_mode correctly detects daemon flag variations #[test] fn is_daemon_mode_detects_daemon_flag() { @@ -2308,7 +2313,7 @@ mod tests { /// Test core_rpc_url returns expected format #[test] fn core_rpc_url_returns_expected_format() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = env_lock(); let original = std::env::var("OPENHUMAN_CORE_RPC_URL").ok(); std::env::set_var("OPENHUMAN_CORE_RPC_URL", "http://localhost:9999/rpc"); @@ -2328,7 +2333,7 @@ mod tests { /// Test overlay_parent_rpc_url handles empty env var #[test] fn overlay_parent_rpc_url_handles_empty() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = env_lock(); let original = std::env::var("OPENHUMAN_CORE_RPC_URL").ok(); std::env::set_var("OPENHUMAN_CORE_RPC_URL", ""); @@ -2531,7 +2536,7 @@ mod tests { #[test] fn sentry_environment_reads_openhuman_app_env() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = env_lock(); let key = "OPENHUMAN_APP_ENV"; let original = std::env::var(key).ok(); std::env::set_var(key, "staging"); @@ -2545,7 +2550,7 @@ mod tests { #[test] fn sentry_environment_trims_whitespace_from_openhuman_app_env() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = env_lock(); let key = "OPENHUMAN_APP_ENV"; let original = std::env::var(key).ok(); std::env::set_var(key, " dev "); @@ -2559,7 +2564,7 @@ mod tests { #[test] fn sentry_environment_skips_empty_openhuman_app_env() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = env_lock(); let key = "OPENHUMAN_APP_ENV"; let original = std::env::var(key).ok(); std::env::set_var(key, ""); @@ -2574,7 +2579,7 @@ mod tests { #[test] fn sentry_environment_skips_whitespace_only_openhuman_app_env() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = env_lock(); let key = "OPENHUMAN_APP_ENV"; let original = std::env::var(key).ok(); std::env::set_var(key, " "); @@ -2591,7 +2596,7 @@ mod tests { /// asserts the hard default when no compile-time override is present. #[test] fn sentry_environment_defaults_to_production_when_unset() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = env_lock(); if option_env!("VITE_OPENHUMAN_APP_ENV").is_some() { // A compile-time override is baked in; skip — the fallback path is // exercised by sentry_environment_skips_empty_openhuman_app_env. diff --git a/src/core/logging.rs b/src/core/logging.rs index f0af1166b6..181c5d4919 100644 --- a/src/core/logging.rs +++ b/src/core/logging.rs @@ -371,8 +371,13 @@ mod tests { /// concurrent env-var writes would race. static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + /// Acquire the test environment lock, recovering from poison if needed. + fn env_lock() -> std::sync::MutexGuard<'static, ()> { + ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()) + } + fn with_clean_rust_log(f: impl FnOnce() -> R) -> R { - let _guard = ENV_LOCK.lock().unwrap(); + let _guard = env_lock(); let prior = std::env::var("RUST_LOG").ok(); std::env::remove_var("RUST_LOG"); let result = f(); @@ -435,7 +440,7 @@ mod tests { #[test] fn seed_rust_log_respects_existing_value() { - let _guard = ENV_LOCK.lock().unwrap(); + let _guard = env_lock(); let prior = std::env::var("RUST_LOG").ok(); std::env::set_var("RUST_LOG", "warn"); seed_rust_log(true, CliLogDefault::Global); @@ -456,7 +461,7 @@ mod tests { #[test] fn parse_log_file_constraints_handles_csv_and_whitespace() { - let _guard = ENV_LOCK.lock().unwrap(); + let _guard = env_lock(); let prior = std::env::var("OPENHUMAN_LOG_FILE_CONSTRAINTS").ok(); std::env::set_var("OPENHUMAN_LOG_FILE_CONSTRAINTS", "rpc, , agent ,memory"); let parsed = parse_log_file_constraints(); diff --git a/src/openhuman/composio/periodic.rs b/src/openhuman/composio/periodic.rs index ca5ead6112..36ff53e9e9 100644 --- a/src/openhuman/composio/periodic.rs +++ b/src/openhuman/composio/periodic.rs @@ -229,7 +229,7 @@ pub(crate) async fn run_one_tick() -> Result<(), String> { #[cfg(test)] mod tests { use super::*; - use crate::openhuman::config::TEST_ENV_LOCK as ENV_LOCK; + use crate::openhuman::config::test_env_lock; use tempfile::tempdir; #[test] @@ -285,7 +285,7 @@ mod tests { async fn run_one_tick_returns_ok_when_no_client() { // Isolate the workspace/env so config loading doesn't contend with // sibling tests mutating OPENHUMAN_WORKSPACE in parallel. - let _guard = ENV_LOCK.lock().expect("env lock"); + let _guard = test_env_lock(); let tmp = tempdir().expect("tempdir"); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); diff --git a/src/openhuman/config/mod.rs b/src/openhuman/config/mod.rs index ef3716c495..217ae35996 100644 --- a/src/openhuman/config/mod.rs +++ b/src/openhuman/config/mod.rs @@ -55,6 +55,15 @@ pub use schemas::{ #[cfg(test)] pub(crate) static TEST_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); +/// Acquire the test environment lock, recovering from poison if needed. +/// When a test panics while holding the lock, the mutex becomes poisoned. +/// This helper clears the poison and returns the guard, allowing subsequent +/// tests to continue rather than cascading failures. +#[cfg(test)] +pub(crate) fn test_env_lock() -> std::sync::MutexGuard<'static, ()> { + TEST_ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/openhuman/config/ops_tests.rs b/src/openhuman/config/ops_tests.rs index 4424d8e2f3..fda6b98328 100644 --- a/src/openhuman/config/ops_tests.rs +++ b/src/openhuman/config/ops_tests.rs @@ -33,11 +33,11 @@ async fn reset_local_data_removes_current_dir_default_dir_and_marker() { // ── env_flag_enabled ──────────────────────────────────────────── -use crate::openhuman::config::TEST_ENV_LOCK as ENV_LOCK; +use crate::openhuman::config::test_env_lock; #[test] fn env_flag_enabled_recognizes_truthy_forms() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let key = "OPENHUMAN_TEST_FLAG_A"; for truthy in ["1", "true", "TRUE", "yes", "YES"] { unsafe { @@ -61,7 +61,7 @@ fn env_flag_enabled_recognizes_truthy_forms() { #[test] fn core_rpc_url_from_env_returns_default_when_unset() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); unsafe { std::env::remove_var("OPENHUMAN_CORE_RPC_URL"); } @@ -70,7 +70,7 @@ fn core_rpc_url_from_env_returns_default_when_unset() { #[test] fn core_rpc_url_from_env_uses_override_when_set() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); unsafe { std::env::set_var("OPENHUMAN_CORE_RPC_URL", "http://1.2.3.4:9999/rpc"); } @@ -116,7 +116,7 @@ fn config_openhuman_dir_returns_config_path_parent() { #[test] fn get_runtime_flags_reads_env_overrides() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); unsafe { std::env::remove_var("OPENHUMAN_BROWSER_ALLOW_ALL"); } @@ -128,7 +128,7 @@ fn get_runtime_flags_reads_env_overrides() { #[test] fn set_browser_allow_all_toggles_env_var() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let before = std::env::var("OPENHUMAN_BROWSER_ALLOW_ALL").ok(); let _ = set_browser_allow_all(true); @@ -503,7 +503,7 @@ async fn get_config_snapshot_wraps_snapshot_in_rpc_outcome() { #[tokio::test] async fn load_and_apply_dictation_settings_rejects_invalid_activation_mode() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); @@ -525,7 +525,7 @@ async fn load_and_apply_dictation_settings_rejects_invalid_activation_mode() { #[tokio::test] async fn load_and_apply_voice_server_settings_rejects_invalid_activation_mode() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); @@ -550,7 +550,7 @@ async fn load_and_apply_voice_server_settings_rejects_invalid_activation_mode() #[tokio::test] async fn load_and_apply_dictation_settings_accepts_valid_modes() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); @@ -576,7 +576,7 @@ async fn load_and_apply_dictation_settings_accepts_valid_modes() { #[tokio::test] async fn load_and_apply_voice_server_settings_accepts_valid_modes_and_clamps() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); @@ -609,7 +609,7 @@ async fn load_and_apply_voice_server_settings_accepts_valid_modes_and_clamps() { #[tokio::test] async fn get_dictation_settings_reads_from_loaded_config() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); @@ -625,7 +625,7 @@ async fn get_dictation_settings_reads_from_loaded_config() { #[tokio::test] async fn get_voice_server_settings_reads_from_loaded_config() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); @@ -640,7 +640,7 @@ async fn get_voice_server_settings_reads_from_loaded_config() { #[tokio::test] async fn get_onboarding_completed_reads_from_loaded_config() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); @@ -655,7 +655,7 @@ async fn get_onboarding_completed_reads_from_loaded_config() { #[tokio::test] async fn load_and_resolve_api_url_returns_api_url_in_response() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); @@ -669,7 +669,7 @@ async fn load_and_resolve_api_url_returns_api_url_in_response() { #[tokio::test] async fn workspace_onboarding_flag_resolve_rejects_invalid_and_defaults() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); @@ -691,7 +691,7 @@ async fn workspace_onboarding_flag_resolve_rejects_invalid_and_defaults() { #[tokio::test] async fn workspace_onboarding_flag_set_rejects_invalid_names() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); @@ -709,7 +709,7 @@ async fn workspace_onboarding_flag_set_rejects_invalid_names() { #[tokio::test] async fn workspace_onboarding_flag_set_round_trip() { - let _g = ENV_LOCK.lock().unwrap(); + let _g = test_env_lock(); let tmp = tempdir().unwrap(); unsafe { std::env::set_var("OPENHUMAN_WORKSPACE", tmp.path()); diff --git a/src/openhuman/credentials/cli.rs b/src/openhuman/credentials/cli.rs index 4a72b10511..02a7b8e06a 100644 --- a/src/openhuman/credentials/cli.rs +++ b/src/openhuman/credentials/cli.rs @@ -106,11 +106,11 @@ pub async fn cli_auth_list(provider_filter: Option) -> Result