Repository: canonical/multipass (2026-04-12T23:16:25Z)

Issue:
Multipass outputs inconsistent number of empty lines on different errors
**Describe the bug**
The CLI outputs inconsistent number of empty lines on different errors.

```plain
ricab@ricab-xps13:~ $ multipass launch --disk 1g
launch failed: Requested disk (1073741824 bytes) below minimum for this image (2361393152 bytes)
ricab@ricab-xps13:~ $ multipass start asdf
start failed: instance(s) missing
Instance 'asdf' does not exist.

ricab@ricab-xps13:~ $ multipass launch --name primary
launch failed: instance "primary" already exists


ricab@ricab-xps13:~ $
```

Zero or one is pretty unnoticeable, but two feels excessive. 

**Additional info**
```
$ multipass version
multipass  1.6.2
multipassd 1.6.2
```


Discussion:
Better now, though not fully fixed:

```
$ multipass launch --disk 1g
launch failed: Requested disk (1073741824 bytes) below minimum for this image (3758096384 bytes)
$ multipass start asdf
start failed: instance(s) missing
Instance 'asdf' does not exist.

$ multipass launch --name primary
launch failed: instance "primary" already exists
$
```
Hi!
I created this https://github.com/canonical/multipass/pull/4817 to solve the issue!



Patch:
diff --git a/src/client/common/client_common.cpp b/src/client/common/client_common.cpp
index d1f6d43bc7..5d0530958b 100644
--- a/src/client/common/client_common.cpp
+++ b/src/client/common/client_common.cpp
@@ -81,6 +81,7 @@ grpc::SslCredentialsOptions get_ssl_credentials_opts_from(const mp::CertProvider
 
     return opts;
 }
+
 } // namespace
 
 mp::ReturnCode mp::cmd::standard_failure_handler_for(const std::string& command,
@@ -88,11 +89,14 @@ mp::ReturnCode mp::cmd::standard_failure_handler_for(const std::string& command,
                                                      const grpc::Status& status,
                                                      const std::string& error_details)
 {
+    const auto trimmed =
+        std::string_view(error_details).substr(0, error_details.find_last_not_of("\r\n") + 1);
+
     fmt::print(cerr,
                "{} failed: {}\n{}",
                command,
                status.error_message(),
-               !error_details.empty() ? fmt::format("{}\n", error_details) : "");
+               trimmed.empty() ? "" : fmt::format("{}\n", trimmed));
 
     return return_code_for(status.error_code());
 }


Test patch:
diff --git a/tests/unit/test_client_common.cpp b/tests/unit/test_client_common.cpp
index fb8c4192f8..19dbafca70 100644
--- a/tests/unit/test_client_common.cpp
+++ b/tests/unit/test_client_common.cpp
@@ -32,6 +32,8 @@
 #include <multipass/cli/client_common.h>
 #include <multipass/utils.h>
 
+#include <grpcpp/support/status.h>
+
 namespace mp = multipass;
 namespace mpt = multipass::test;
 
@@ -129,3 +131,18 @@ TEST(TestClientHandleUserPassword, defaultHasNoPassword)
 
     mp::cmd::handle_password(client.get(), &term);
 }
+
+TEST(StandardFailureHandlerFormatting, singleTrailingNewlineWhenDetailsAlreadyEndWithNewline)
+{
+    std::stringstream cerr_stream;
+    grpc::Status status{grpc::StatusCode::ABORTED, "instance(s) missing", ""};
+
+    mp::cmd::standard_failure_handler_for("start",
+                                          cerr_stream,
+                                          status,
+                                          "Instance 'asdf' does not exist.\r\n\r\n\r");
+
+    EXPECT_EQ(cerr_stream.str(),
+              "start failed: instance(s) missing\n"
+              "Instance 'asdf' does not exist.\n");
+}