From e8757c85e218c5a72eb6221ce9c5f6e8ce07345a Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Mon, 13 Apr 2026 00:41:35 -0400 Subject: [PATCH] [2.x] fix: Fixes client-side run status (#9081) **Problem** In sbt 2.x, if we execute a run task from the shell, and if the program fails, it ends up taking down the entire shell because client-side run rethrows, which is not desirable for the sbt shell. **Solution** 1. Omit printing out success for ClientJobParams, which is the runinfo. 2. Print out success or error on the client-side for shell usage case. --- .../sbt/internal/client/NetworkClient.scala | 21 ++++++++++++++----- .../main/scala/sbt/internal/Aggregation.scala | 11 ++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index 5d5f089e0..399b3cdd4 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -179,6 +179,7 @@ class NetworkClient( def success(message: => String): Unit = () def log(level: Level.Value, message: => String): Unit = console.appendLog(level, message) } + private val interactive = arguments.commandArguments.isEmpty private[sbt] def connectOrStartServerAndConnect( promptCompleteUsers: Boolean, @@ -706,8 +707,19 @@ class NetworkClient( case (`clientJob`, Some(json)) => import sbt.internal.worker.codec.JsonProtocol.given Converter.fromJson[ClientJobParams](json) match { - case Success(params) => clientSideRun(params).get; Vector.empty - case Failure(_) => Vector.empty + case Success(params) => + clientSideRun(params) match + case Success(_) => + if interactive then console.success("ok") + else () + Vector.empty + case Failure(e) => + if interactive then + Vector( + (Level.Error, e.getMessage) + ) + else throw e + case Failure(_) => Vector.empty } case (`Shutdown`, Some(_)) => Vector.empty case (msg, _) if msg.startsWith("build/") => Vector.empty @@ -915,9 +927,8 @@ class NetworkClient( withSignalHandler(contHandler, Signals.CONT) { interactiveThread.set(Thread.currentThread) val cleaned = arguments.commandArguments - val userCommands = cleaned.takeWhile(_ != TerminateAction) - val interactive = cleaned.isEmpty - val exit = cleaned.nonEmpty && userCommands.isEmpty + val userCommands = arguments.commandArguments.takeWhile(_ != TerminateAction) + val exit = arguments.commandArguments.nonEmpty && userCommands.isEmpty attachUUID.set(sendJson(attach, s"""{"interactive": $interactive}""")) val handler: () => Unit = () => { def exitAbruptly() = { diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index bdde24c48..6e1105484 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -17,6 +17,7 @@ import sbt.internal.util.complete.Parser import sbt.internal.util.complete.Parser.{ failure, seq, success } import sbt.internal.util.* import sbt.internal.client.NetworkClient +import sbt.internal.worker.ClientJobParams import sbt.std.Transform.DummyTaskMap import sbt.util.{ Logger, Show } import scala.annotation.tailrec @@ -87,16 +88,18 @@ object Aggregation { import complete.* val log = state.log val extracted = Project.extract(state) - val success = results match - case Result.Value(_) => true - case Result.Inc(_) => false + // omit success printing for client-side run + val (success, jobParams) = results match + case Result.Value(Seq(KeyValue(_, p: ClientJobParams))) => (true, true) + case Result.Value(_) => (true, false) + case Result.Inc(_) => (false, false) val isPaused = currentChannel(state) match case Some(channel) => channel.isPaused case None => false results.toEither.foreach { r => if show.taskValues then printSettings(r, show.print) else () } - if !isPaused && show.success && !state.get(suppressShow).getOrElse(false) then + if !isPaused && show.success && !state.get(suppressShow).getOrElse(false) && !jobParams then printSuccess(start, stop, extracted, success, cacheSummary, log) else ()