From bf678f2d6ac2753979cc8ec67420d9c4862f4f0b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 25 Apr 2026 03:36:32 -0400 Subject: [PATCH] [2.x] fix: Fixes ctrl-c handling **Problem** Ctrl-C during client-side run gets interpreted in sbtn, and shuts down the shell. **Solution** Ignore ctrl-c during client-side run. --- .../sbt/internal/client/NetworkClient.scala | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 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 7b0b97183..724aed370 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -169,6 +169,7 @@ class NetworkClient( private val inLock = new Object private val inputThread = new AtomicReference[RawInputThread] private val exitClean = new AtomicBoolean(true) + private val inClientSideRun = new AtomicBoolean(false) private val sbtProcess = new AtomicReference[Process](null) private class ConnectionRefusedException(t: Throwable) extends Throwable(t) private class ServerFailedException extends Exception @@ -816,9 +817,12 @@ class NetworkClient( } Run.processExitCode(exitCode, "runner") } - if (runInfo.jvm) - RunHandler.jvmRun(runInfo.jvmRunInfo.getOrElse(sys.error("missing jvmRunInfo")), log) - else nativeRun(runInfo.nativeRunInfo.getOrElse(sys.error("missing nativeRunInfo"))) + inClientSideRun.set(true) + try + if (runInfo.jvm) + RunHandler.jvmRun(runInfo.jvmRunInfo.getOrElse(sys.error("missing jvmRunInfo")), log) + else nativeRun(runInfo.nativeRunInfo.getOrElse(sys.error("missing nativeRunInfo"))) + finally inClientSideRun.set(false) } def onRequest(msg: JsonRpcRequestMessage): Unit = { @@ -941,14 +945,15 @@ class NetworkClient( exitClean.set(false) close() } - if (cancelled.compareAndSet(false, true)) { + if inClientSideRun.get() then () + else if cancelled.compareAndSet(false, true) then val cancelledTasks = { val queue = sendCancelAllCommand() Option(queue.poll(1, TimeUnit.SECONDS)).getOrElse(true) } - if ((batchMode.get && pendingResults.isEmpty) || !cancelledTasks) exitAbruptly() + if (batchMode.get && pendingResults.isEmpty) || !cancelledTasks then exitAbruptly() else cancelled.set(false) - } else exitAbruptly() // handles double ctrl+c to force a shutdown + else exitAbruptly() // handles double ctrl+c to force a shutdown } withSignalHandler(handler, Signals.INT) { def block(): Int = {