From 05cd00e13544f4c6d2be50777bfdccdbf7762186 Mon Sep 17 00:00:00 2001 From: BitToby <218712309+bittoby@users.noreply.github.com> Date: Mon, 13 Apr 2026 00:24:30 +0200 Subject: [PATCH] [2.x] fix: stack traces suppressed in thin client batch mode (#9058) In sbt 2.x, running batch commands through the thin client (sbtn) suppresses stack traces for all tasks because the server's shell command unconditionally sets state.interactive = true. This causes LogManager.defaultTraceLevel to return -1 (suppressed) even when the client explicitly signals non-interactive (batch) mode via Attach(interactive=false). This fixes the shell command to check the originating NetworkChannel's interactive flag before setting state.interactive, so thin client batch commands correctly get Int.MaxValue trace level and display full stack traces. --- main/src/main/scala/sbt/Main.scala | 10 +++++++++- .../sbt-test/actions/streams-trace-level/build.sbt | 11 +++++++++++ sbt-app/src/sbt-test/actions/streams-trace-level/test | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 sbt-app/src/sbt-test/actions/streams-trace-level/build.sbt create mode 100644 sbt-app/src/sbt-test/actions/streams-trace-level/test diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index f7bbb12b9..37fe90b49 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -1144,12 +1144,20 @@ object BuiltinCommands { .getOpt(Keys.minForcegcInterval) .getOrElse(GCUtil.defaultMinForcegcInterval) val exec: Exec = getExec(s1, minGCInterval) + val isInteractive = exec.source match { + case Some(src) if src.channelName.startsWith("network") => + exchange.channelForName(src.channelName) match { + case Some(nc: NetworkChannel) => nc.isInteractive + case _ => true + } + case _ => true + } val newState = s1 .copy( onFailure = Some(Exec(Shell, None)), remainingCommands = exec +: Exec(Shell, None) +: s1.remainingCommands ) - .setInteractive(true) + .setInteractive(isInteractive) val res = if (exec.commandLine.trim.isEmpty) newState else newState.clearGlobalLog diff --git a/sbt-app/src/sbt-test/actions/streams-trace-level/build.sbt b/sbt-app/src/sbt-test/actions/streams-trace-level/build.sbt new file mode 100644 index 000000000..e2b32014e --- /dev/null +++ b/sbt-app/src/sbt-test/actions/streams-trace-level/build.sbt @@ -0,0 +1,11 @@ +lazy val helloWithoutStreams = taskKey[Unit]("") +lazy val helloWithStreams = taskKey[Unit]("") + +helloWithoutStreams := { + throw new RuntimeException("boom without streams!") +} + +helloWithStreams := { + val log = streams.value.log + throw new RuntimeException("boom with streams!") +} diff --git a/sbt-app/src/sbt-test/actions/streams-trace-level/test b/sbt-app/src/sbt-test/actions/streams-trace-level/test new file mode 100644 index 000000000..92bc6373f --- /dev/null +++ b/sbt-app/src/sbt-test/actions/streams-trace-level/test @@ -0,0 +1,2 @@ +-> helloWithoutStreams +-> helloWithStreams