From c4a88328da7afd0e1d94fd8eab42d74d6a16baf3 Mon Sep 17 00:00:00 2001 From: calm <148254234+calm329@users.noreply.github.com> Date: Fri, 6 Feb 2026 05:36:07 -0600 Subject: [PATCH] [2.x] fix: handle NoSuchFileException during cache storage (#8699) --- .../src/main/scala/sbt/util/ActionCache.scala | 64 ++++++++++--------- .../main/scala/sbt/util/CacheImplicits.scala | 2 +- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/util-cache/src/main/scala/sbt/util/ActionCache.scala b/util-cache/src/main/scala/sbt/util/ActionCache.scala index f7331a469..25dd8cbea 100644 --- a/util-cache/src/main/scala/sbt/util/ActionCache.scala +++ b/util-cache/src/main/scala/sbt/util/ActionCache.scala @@ -10,7 +10,7 @@ package sbt.util import java.io.File import java.nio.charset.StandardCharsets -import java.nio.file.{ Files, Path, Paths, StandardCopyOption } +import java.nio.file.{ Files, NoSuchFileException, Path, Paths, StandardCopyOption } import sbt.internal.util.{ ActionCacheEvent, CacheEventLog, StringVirtualFile1 } import sbt.io.syntax.* import sbt.io.IO @@ -81,34 +81,40 @@ object ActionCache: case e: Exception => cacheEventLog.append(ActionCacheEvent.Error) throw e - val json = Converter.toJsonUnsafe(result) - val normalizedOutputDir = outputDirectory.toAbsolutePath.normalize() - val uncacheableOutputs = - outputs.filter(f => - f match - case vf if vf.id.endsWith(ActionCache.dirZipExt) => - false - case _ => - val outputPath = fileConverter.toPath(f).toAbsolutePath.normalize() - !outputPath.startsWith(normalizedOutputDir) - ) - if uncacheableOutputs.nonEmpty then - cacheEventLog.append(ActionCacheEvent.Error) - logger.error( - s"Cannot cache task because its output files are outside the output directory: \n" + - uncacheableOutputs.mkString(" - ", "\n - ", "") - ) - result - else - cacheEventLog.append(ActionCacheEvent.OnsiteTask) - val (input, valuePath) = mkInput(key, codeContentHash, extraHash) - val valueFile = StringVirtualFile1(valuePath, CompactPrinter(json)) - val newOutputs = Vector(valueFile) ++ outputs.toVector - store.put(UpdateActionResultRequest(input, newOutputs, exitCode = 0)) match - case Right(cachedResult) => - store.syncBlobs(cachedResult.outputFiles, outputDirectory) - result - case Left(e) => throw e + try + val json = Converter.toJsonUnsafe(result) + val normalizedOutputDir = outputDirectory.toAbsolutePath.normalize() + val uncacheableOutputs = + outputs.filter(f => + f match + case vf if vf.id.endsWith(ActionCache.dirZipExt) => + false + case _ => + val outputPath = fileConverter.toPath(f).toAbsolutePath.normalize() + !outputPath.startsWith(normalizedOutputDir) + ) + if uncacheableOutputs.nonEmpty then + cacheEventLog.append(ActionCacheEvent.Error) + logger.error( + s"Cannot cache task because its output files are outside the output directory: \n" + + uncacheableOutputs.mkString(" - ", "\n - ", "") + ) + result + else + cacheEventLog.append(ActionCacheEvent.OnsiteTask) + val (input, valuePath) = mkInput(key, codeContentHash, extraHash) + val valueFile = StringVirtualFile1(valuePath, CompactPrinter(json)) + val newOutputs = Vector(valueFile) ++ outputs.toVector + store.put(UpdateActionResultRequest(input, newOutputs, exitCode = 0)) match + case Right(cachedResult) => + store.syncBlobs(cachedResult.outputFiles, outputDirectory) + result + case Left(e) => throw e + catch + case e: NoSuchFileException => + logger.debug(s"Skipping cache storage due to missing file: ${e.getMessage}") + cacheEventLog.append(ActionCacheEvent.Error) + result // Single cache lookup - use exitCode to distinguish success from failure getWithFailure(key, codeContentHash, extraHash, tags, config) match diff --git a/util-cache/src/main/scala/sbt/util/CacheImplicits.scala b/util-cache/src/main/scala/sbt/util/CacheImplicits.scala index 3a7648906..7e3ec8c6b 100644 --- a/util-cache/src/main/scala/sbt/util/CacheImplicits.scala +++ b/util-cache/src/main/scala/sbt/util/CacheImplicits.scala @@ -74,6 +74,6 @@ trait CacheImplicits extends BasicCacheImplicits with BasicJsonProtocol: val lastModified = attrs.lastModifiedTime().toMillis() val sizeBytes = attrs.size() getOrElseUpdate(ref, lastModified, sizeBytes)(fallback) - catch case _: NoSuchFileException => fallback + catch case e: NoSuchFileException => throw e case _ => fallback end CacheImplicits