From 7a07e46c55070f425a29b820068d785654918e05 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Wed, 11 Mar 2026 11:45:17 +0100 Subject: [PATCH] fix(appd): broadcast AppState::Stopped on runtime spawn failure If WEFT_RUNTIME_BIN is set but the binary cannot be spawned (missing, not executable, etc.), supervise() now transitions the session to Stopped and broadcasts AppState::Stopped instead of returning an error that left the session permanently stuck in Starting. --- crates/weft-appd/src/runtime.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/weft-appd/src/runtime.rs b/crates/weft-appd/src/runtime.rs index 0d5a526..5269123 100644 --- a/crates/weft-appd/src/runtime.rs +++ b/crates/weft-appd/src/runtime.rs @@ -1,6 +1,5 @@ use std::time::Duration; -use anyhow::Context; use tokio::io::{AsyncBufReadExt, BufReader}; use crate::Registry; @@ -22,14 +21,26 @@ pub(crate) async fn supervise( } }; - let mut child = tokio::process::Command::new(&bin) + let mut child = match tokio::process::Command::new(&bin) .arg(app_id) .arg(session_id.to_string()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .stdin(std::process::Stdio::null()) .spawn() - .with_context(|| format!("spawn {bin}"))?; + { + Ok(c) => c, + Err(e) => { + tracing::warn!(session_id, %app_id, error = %e, "failed to spawn runtime; marking session stopped"); + let mut reg = registry.lock().await; + reg.set_state(session_id, AppStateKind::Stopped); + let _ = reg.broadcast().send(Response::AppState { + session_id, + state: AppStateKind::Stopped, + }); + return Ok(()); + } + }; let stdout = child.stdout.take().expect("stdout piped"); let stderr = child.stderr.take().expect("stderr piped");