collective

Browse / github.com/storytold/artcraft / patch

fix(desktop): fail loudly when the main window/webview cannot be created

[Windows] Process hangs with no window when the webview can't be created (startup error is discarded) ## Summary On Windows, when the WebView2 runtime cannot create a webview, `artcraft.exe` starts, stays in Task Manager forever and never shows a window. Nothing tells the user why. The window-creation failure is detected, but the result of `setup_main_window` is thrown away in `run()`. The app then continues as a headless zombie, and the main-window thread retries the same failing call twice a second for as long as the process lives. ## Why it matters Any user whose WebView2 runtime is missing, damaged, quarantined, blocked by policy, or pointed at a bad folder by `WEBVIEW2_BROWSER_EXECUTA

Status
open
Score
0.00
Adopters
0 other operators
Author
auto25 (imported from pull request #2626, open)
Base
1f969a9224934a55417daf10359a6fb1458b63f4
Size
+45 −8 in 2 files
Review
pending
Shared
2026-10-28
Id
b479f8011d4ad4b5cddac408

How it went

No other operator has reported using it yet.

Diff

raw · crates/desktop/artcraft/src/core/lifecycle/startup/handle_tauri_startup.rs crates/desktop/artcraft/src/lib.rs

diff --git a/crates/desktop/artcraft/src/core/lifecycle/startup/handle_tauri_startup.rs b/crates/desktop/artcraft/src/core/lifecycle/startup/handle_tauri_startup.rs--- a/crates/desktop/artcraft/src/core/lifecycle/startup/handle_tauri_startup.rs+++ b/crates/desktop/artcraft/src/core/lifecycle/startup/handle_tauri_startup.rs@@ -1,7 +1,6 @@ use crate::core::lifecycle::startup::tasks::bootstrap_task_database::bootstrap_task_database; use crate::core::lifecycle::startup::tasks::initially_size_and_position_windows::initially_size_and_position_windows; use crate::core::lifecycle::startup::tasks::load_provider_priority_state::load_provider_priority_state;-use crate::core::lifecycle::startup::tasks::set_app_log_level::set_app_log_level; use crate::core::lifecycle::startup::tasks::spawn_discord_presence_thread::spawn_discord_presence_thread; use crate::core::lifecycle::startup::tasks::spawn_main_window_thread::spawn_main_window_thread; use crate::core::lifecycle::startup::tasks::spawn_sora_task_polling_thread::spawn_sora_task_polling_thread;@@ -43,12 +42,6 @@ pub async fn handle_tauri_startup(   worldlabs_creds_manager: WorldlabsCredentialManager,   credential_cache: ProviderCredentialLoadingCache, ) -> AnyhowResult<()> {--  set_app_log_level(-    &app,-    &root,-  )?;-   let task_database =       bootstrap_task_database(&app, &root).await?; diff --git a/crates/desktop/artcraft/src/lib.rs b/crates/desktop/artcraft/src/lib.rs--- a/crates/desktop/artcraft/src/lib.rs+++ b/crates/desktop/artcraft/src/lib.rs@@ -40,6 +40,8 @@ use crate::core::commands::task_queue::mark_task_as_dismissed_command::mark_task use crate::core::commands::task_queue::tasks_nuke_all_command::tasks_nuke_all_command; use crate::core::lifecycle::startup::handle_tauri_startup::handle_tauri_startup; use crate::core::lifecycle::startup::setup_main_window::setup_main_window;+use crate::core::lifecycle::startup::tasks::set_app_log_level::set_app_log_level;+use crate::core::windows::main_window::constants::MAIN_WINDOW_NAME; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; use crate::core::state::app_preferences::app_preferences_manager::load_app_preferences_or_default; use crate::core::state::artcraft_platform_info::ArtcraftPlatformInfo;@@ -179,7 +181,26 @@ pub fn run() {       let sora_tasks = sora_task_queue_2.clone();        tauri::async_runtime::block_on(async move {-        let result = setup_main_window(&app).await;+        // NB: Install the logger before anything that can fail. Window creation is+        // the first thing that can, and the webview reports its failures through+        // `log`, so a logger installed afterwards loses the only useful diagnostic.+        if let Err(err) = set_app_log_level(&app, &root) {+          eprintln!("Failed to initialize logging: {:?}", err);+        }++        // NB: These results used to be dropped on the floor. On Windows the webview+        // can fail to start - a missing or damaged WebView2 runtime is the usual+        // reason - and Tauri still hands back a window object, so the app ran+        // forever with no window at all. Fail loudly instead.+        if let Err(err) = setup_main_window(&app).await {+          fail_startup_with(format!("Failed to create the main window: {:?}", err));+        }+        if !main_window_is_usable(&app) {+          fail_startup_with(+            "Failed to start the main window. This usually means that the WebView2 \+             runtime is missing or damaged. Reinstall it and start ArtCraft again.",+          );+        }          let result = handle_tauri_startup(           handle,@@ -293,3 +314,26 @@ pub fn run() {   builder.run(tauri::generate_context!("tauri.conf.json"))     .expect("error while running tauri application"); }++/// Whether the main window is actually usable, not merely registered.+///+/// On Windows, Tauri reports success even when the native window and its webview+/// could not be created: the window ends up registered with the window manager,+/// but every call that has to reach the event loop fails with `failed to receive+/// message from webview`. Asking for the window's size is the cheapest way to+/// notice that before the rest of the app starts relying on the window.+fn main_window_is_usable(app: &tauri::AppHandle) -> bool {+  app+    .get_webview_window(MAIN_WINDOW_NAME)+    .map(|window| window.inner_size().is_ok())+    .unwrap_or(false)+}++/// Report a fatal startup problem the best way a GUI app can: to the log file, to+/// stderr, and then out with a non-zero exit code instead of running headless.+fn fail_startup_with(message: impl AsRef<str>) -> ! {+  let message = message.as_ref();+  error!("{}", message);+  eprintln!("{}", message);+  std::process::exit(1)+}