From 1b38bdb5a1de127ca8040f5a132d08c73ccb3c67 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 20 Jun 2026 15:37:24 +0900 Subject: refactor(console): propagate getComposer's exit(1) as ExitException Composer's Application::getComposer() calls exit(1) when a required Composer instance fails to build under areExceptionsCaught(). Replace the deep std::process::exit with a php-shim ExitException that mirrors PHP's `exit` construct: it bypasses parent::doRun()'s catch and Symfony's renderThrowable (excluded at both broad catches), so the already-written plain error message is not re-rendered and the process exits with code 1. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/shirabe/src/console/application.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'crates/shirabe/src/console/application.rs') diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs index 6622a2d..9c3ef01 100644 --- a/crates/shirabe/src/console/application.rs +++ b/crates/shirabe/src/console/application.rs @@ -953,6 +953,13 @@ impl Application { let outcome = match result_outcome { Ok(r) => Ok(r), Err(e) => { + // PHP's `exit` bypasses parent::doRun()'s catch entirely; re-raise it untouched so + // the GitHub Actions annotation and error hints below are skipped. + if e.downcast_ref::() + .is_some() + { + return Err(e); + } if let Some(see) = e.downcast_ref::() { if application.borrow().get_disable_plugins_by_default() && application.borrow().is_running_as_root() @@ -1203,7 +1210,10 @@ impl Application { if required { self.io.write_error(&e.to_string()); if self.are_exceptions_caught() { - std::process::exit(1); + // PHP calls `exit(1)` here, terminating before parent::run() can + // re-render the exception. Propagate it as an ExitException so the + // top-level handler turns it into exit code 1 without re-rendering. + return Err(shirabe_php_shim::ExitException { code: 1 }.into()); } return Err(e); } @@ -1471,6 +1481,12 @@ impl Application { let exit_code = match result { Ok(exit_code) => exit_code, Err(e) => { + // PHP's `exit` bypasses Symfony's try/catch: it never renders and forces the exit + // code it carries. The message, if any, has already been written at the exit site. + if let Some(exit) = e.downcast_ref::() { + return Ok(exit.code as i32); + } + if !application.borrow().catch_exceptions { return Err(e); } -- cgit v1.3.1