[2.x] fix: Print server stderr on startup failure (#8816)

**Problem**
When the sbt server fails to start (e.g. wrong JDK version), the client
only shows "failed to connect to server" hiding the actual error. The
server stderr is redirected to /dev/null to prevent Linux pipe buffer
deadlocks (#8442), so diagnostic output is lost.

**Solution**
Redirect server stderr to a temp file instead of /dev/null. When the
server fails to start (portfile never appears), read and print the temp
file contents before throwing ServerFailedException. The temp file is
cleaned up eagerly on both success and failure paths.

Fixes #8812
This commit is contained in:
Dream 2026-02-26 00:31:09 -05:00 committed by GitHub
parent 32c5637dc9
commit 8d9a0027e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 24 additions and 4 deletions

View File

@ -347,6 +347,7 @@ class NetworkClient(
else None
val term = Terminal.console
term.exitRawMode()
var serverStderrFile: Option[File] = None
val process = socket match {
case None if startServer =>
if (log) console.appendLog(Level.Info, "server was not detected. starting an instance")
@ -393,15 +394,19 @@ class NetworkClient(
// https://github.com/sbt/sbt/issues/8442
// On Linux, if stdout/stderr are inherited and the buffer fills up (~64KB),
// the server process will block on writes. Discard output since the server
// communicates via the boot socket, not stdout/stderr.
// the server process will block on writes. Redirect to files instead of
// inheriting or piping to avoid buffer deadlocks while still capturing
// errors for diagnostics (https://github.com/sbt/sbt/issues/8812).
val nullFile = new File(if (Util.isWindows) "NUL" else "/dev/null")
val stderrFile = Files.createTempFile("sbt-server-err", ".log").toFile
stderrFile.deleteOnExit()
serverStderrFile = Some(stderrFile)
val processBuilder =
new ProcessBuilder((nohup ++ cmd)*)
.directory(arguments.baseDirectory)
.redirectInput(Redirect.PIPE)
.redirectOutput(nullFile)
.redirectError(nullFile)
.redirectError(stderrFile)
processBuilder.environment.put(Terminal.TERMINAL_PROPS, props)
Try(processBuilder.start()) match {
case Success(process) =>
@ -540,7 +545,22 @@ class NetworkClient(
sbtProcess.set(null)
Util.ignoreResult(Runtime.getRuntime.removeShutdownHook(shutdown))
}
if (!portfile.exists()) throw new ServerFailedException
if (!portfile.exists()) {
// Print captured server stderr so users can see why the server failed to start
for (errFile <- serverStderrFile) {
try {
val bytes = Files.readAllBytes(errFile.toPath)
if (bytes.nonEmpty) {
errorStream.write(bytes)
errorStream.flush()
}
} catch { case _: Exception => }
finally errFile.delete()
}
throw new ServerFailedException
}
// Clean up stderr temp file on successful startup
serverStderrFile.foreach(_.delete())
if (attached.get && !stdinBytes.isEmpty) Option(inputThread.get).foreach(_.drain())
}