From 6ad89c29e5a19cf566b88b5fa9eec348f58bb50d Mon Sep 17 00:00:00 2001 From: Vim Wickramasinghe Date: Wed, 7 Jan 2026 11:40:44 +0100 Subject: [PATCH 1/3] [TOW-1331] Expose exit code on crash for local app run --- crates/tower-cmd/src/run.rs | 14 ++++++++------ crates/tower-runtime/src/local.rs | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/tower-cmd/src/run.rs b/crates/tower-cmd/src/run.rs index dc7eb4e0..50a3de07 100644 --- a/crates/tower-cmd/src/run.rs +++ b/crates/tower-cmd/src/run.rs @@ -193,7 +193,10 @@ where // Wait for app to complete or SIGTERM let status_result = tokio::select! { - status = status_task => status.unwrap(), + status = status_task => { + debug!("Status task completed, result: {:?}", status); + status.unwrap() + }, _ = tokio::signal::ctrl_c(), if !output::get_output_mode().is_mcp() => { output::write("\nReceived Ctrl+C, stopping local run...\n"); app.lock().await.terminate().await.ok(); @@ -205,8 +208,8 @@ where // And if we crashed, err out match status_result { Status::Exited => output::success("Your local run exited cleanly."), - Status::Crashed { .. } => { - output::error("Your local run crashed!"); + Status::Crashed { code } => { + output::error(&format!("Your local run crashed with exit code: {}", code)); return Err(Error::AppCrashed); } _ => { @@ -611,20 +614,19 @@ async fn monitor_local_status(app: Arc>) -> Status { err_count = 0; match status { - tower_runtime::Status::Exited => { + Status::Exited => { debug!("Run exited cleanly, stopping status monitoring"); // We're done. Exit this loop and function. return status; } - tower_runtime::Status::Crashed { .. } => { + Status::Crashed { .. } => { debug!("Run crashed, stopping status monitoring"); // We're done. Exit this loop and function. return status; } _ => { - debug!("App status: other, continuing to monitor"); sleep(Duration::from_millis(100)).await; } } diff --git a/crates/tower-runtime/src/local.rs b/crates/tower-runtime/src/local.rs index 5119b086..765c29b7 100644 --- a/crates/tower-runtime/src/local.rs +++ b/crates/tower-runtime/src/local.rs @@ -536,16 +536,25 @@ async fn wait_for_process( if let Ok(res) = timeout { if let Ok(status) = res { - break status.code().expect("no status code"); + let exit_code = status.code().expect("no status code"); + if exit_code != 0 { + debug!(ctx: &ctx, "process completed with non-zero exit code: {}", exit_code); + } + break exit_code; } else { // something went wrong. - debug!(ctx: &ctx, "failed to get status due to some kind of IO error: {}" , res.err().expect("no error somehow")); + let err = res.err().expect("no error somehow"); + debug!(ctx: &ctx, "failed to get status due to some kind of IO error: {}", err); break -1; } } }; - debug!(ctx: &ctx, "process exited with code {}", code); + if code == 0 { + debug!(ctx: &ctx, "process exited cleanly with code 0"); + } else { + debug!(ctx: &ctx, "process exited with error code {}", code); + } // this just shuts up the compiler about ignoring the results. code From 72927d29f4c6f4069d94306ed49ee805d71bdcbe Mon Sep 17 00:00:00 2001 From: Vim Wickramasinghe Date: Wed, 7 Jan 2026 11:44:10 +0100 Subject: [PATCH 2/3] [TOW-1331] Expose exit code on crash for local app run --- crates/tower-runtime/src/local.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/crates/tower-runtime/src/local.rs b/crates/tower-runtime/src/local.rs index 765c29b7..5119b086 100644 --- a/crates/tower-runtime/src/local.rs +++ b/crates/tower-runtime/src/local.rs @@ -536,25 +536,16 @@ async fn wait_for_process( if let Ok(res) = timeout { if let Ok(status) = res { - let exit_code = status.code().expect("no status code"); - if exit_code != 0 { - debug!(ctx: &ctx, "process completed with non-zero exit code: {}", exit_code); - } - break exit_code; + break status.code().expect("no status code"); } else { // something went wrong. - let err = res.err().expect("no error somehow"); - debug!(ctx: &ctx, "failed to get status due to some kind of IO error: {}", err); + debug!(ctx: &ctx, "failed to get status due to some kind of IO error: {}" , res.err().expect("no error somehow")); break -1; } } }; - if code == 0 { - debug!(ctx: &ctx, "process exited cleanly with code 0"); - } else { - debug!(ctx: &ctx, "process exited with error code {}", code); - } + debug!(ctx: &ctx, "process exited with code {}", code); // this just shuts up the compiler about ignoring the results. code From 87ec1bae14f61eded03dc312f8e6c1ed6f73f2b3 Mon Sep 17 00:00:00 2001 From: Vim Wickramasinghe Date: Thu, 8 Jan 2026 10:34:47 +0100 Subject: [PATCH 3/3] fix integration test --- tests/integration/features/steps/cli_steps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/features/steps/cli_steps.py b/tests/integration/features/steps/cli_steps.py index da8ef117..24a510ec 100644 --- a/tests/integration/features/steps/cli_steps.py +++ b/tests/integration/features/steps/cli_steps.py @@ -118,7 +118,7 @@ def step_final_status_should_show_crashed_in_red(context): red_color_code in output ), f"Expected red color codes in output, got: {output}" assert ( - "Your local run crashed!" in output + "Your local run crashed with exit code:" in output ), f"Expected 'Your local run crashed!' message, got: {output}"