diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a8177e7bc54..4fd97fd0f56 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -249,6 +249,8 @@ default:
# GitLab's cache helper restores .gradle as root, but we run as non-root-user (uid 1001),
# and Gradle does `chmod 700 .gradle` on startup which requires user ownership.
- sudo chown -R 1001:1001 .gradle
+ # Detect and fix corrupted Gradle cache: "FATAL: unexpected EOF"
+ - .gitlab/gradle-cache/mitigate_corrupted_gradle_cache.sh "$GRADLE_VERSION"
after_script:
- *cgroup_info
- *container_info
diff --git a/.gitlab/gradle-cache/CorruptedGradleCacheMitigator.java b/.gitlab/gradle-cache/CorruptedGradleCacheMitigator.java
new file mode 100644
index 00000000000..589e4d8b964
--- /dev/null
+++ b/.gitlab/gradle-cache/CorruptedGradleCacheMitigator.java
@@ -0,0 +1,136 @@
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * Band-aid for "FATAL: unexpected EOF" during GitLab cache extraction.
+ *
+ * This removes only the damaged workspaces so Gradle regenerates them.
+ */
+class CorruptedGradleCacheMitigator {
+ private static final Path CACHES_DIR = Path.of(".gradle/caches");
+
+ // Immutable-workspace categories and the directory depth at which their workspaces live.
+ private static final Map WORKSPACE_CATEGORIES =
+ Map.of("dependencies-accessors", 1, "groovy-dsl", 1, "kotlin-dsl", 2, "transforms", 1);
+
+ // Gradle temporary workspaces are - and may legitimately lack metadata.bin.
+ private static final Pattern TEMPORARY_WORKSPACE =
+ Pattern.compile(
+ ".*-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
+
+ // Gradle's own metadata reader, resolved reflectively.
+ private static Object metadataStore;
+ private static Method metadataLoadMethod;
+
+ public static void main(String[] args) throws IOException {
+ var gradleVersion = args[0];
+
+ var damaged = new ArrayList();
+ try {
+ loadMetadataReader();
+ } catch (Throwable e) {
+ System.out.println("Gradle metadata reader unavailable; leaving cache unchanged");
+ e.printStackTrace();
+ return;
+ }
+
+ try {
+ for (var workspace : enumerateWorkspaces(gradleVersion)) {
+ if (isDamaged(workspace)) {
+ damaged.add(workspace);
+ }
+ }
+ } catch (Throwable e) {
+ System.out.println("Failed to collect damaged workspaces");
+ e.printStackTrace();
+ return;
+ }
+
+ if (!damaged.isEmpty()) {
+ System.out.println("Damaged Gradle metadata found, removing:");
+
+ for (var workspace : damaged) {
+ System.out.println(" - " + workspace);
+ try {
+ remove(workspace);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private static List enumerateWorkspaces(String gradleVersion) throws IOException {
+ var versionDir = CACHES_DIR.resolve(gradleVersion);
+ var workspaces = new ArrayList();
+ for (var category : WORKSPACE_CATEGORIES.entrySet()) {
+ collectAtDepth(versionDir.resolve(category.getKey()), category.getValue(), workspaces);
+ }
+ return workspaces;
+ }
+
+ private static void collectAtDepth(Path dir, int depth, List out) throws IOException {
+ if (!Files.isDirectory(dir)) {
+ return;
+ }
+
+ if (depth == 0) {
+ out.add(dir);
+ return;
+ }
+
+ try (var entries = Files.list(dir)) {
+ for (var child : entries.filter(Files::isDirectory).collect(Collectors.toList())) {
+ collectAtDepth(child, depth - 1, out);
+ }
+ }
+ }
+
+ private static void loadMetadataReader() {
+ try {
+ var storeClass = Class.forName(
+ "org.gradle.internal.execution.history.impl.DefaultImmutableWorkspaceMetadataStore");
+ metadataStore = storeClass.getDeclaredConstructor().newInstance();
+ metadataLoadMethod = storeClass.getMethod("loadWorkspaceMetadata", File.class);
+ } catch (Throwable e) {
+ throw new IllegalStateException("Failed to load Gradle metadata reader", e);
+ }
+ }
+
+ private static boolean isDamaged(Path workspace) {
+ if (TEMPORARY_WORKSPACE.matcher(workspace.getFileName().toString()).matches()) {
+ return false;
+ }
+
+ if (!Files.isRegularFile(workspace.resolve("metadata.bin"))) {
+ return true;
+ }
+
+ // A successful return means Gradle's own reader fully deserialized `metadata.bin`.
+ try {
+ metadataLoadMethod.invoke(metadataStore, workspace.toFile());
+ return false;
+ } catch (Throwable e) {
+ return true; // truncated/unreadable -> remove it
+ }
+ }
+
+ private static void remove(Path workspace) {
+ try (var paths = Files.walk(workspace)) {
+ for (var path : paths.sorted(Comparator.reverseOrder()).collect(Collectors.toList())) {
+ Files.deleteIfExists(path);
+ }
+ } catch (Throwable e) {
+ throw new IllegalStateException("Failed to remove: " + workspace, e);
+ }
+ }
+}
diff --git a/.gitlab/gradle-cache/mitigate_corrupted_gradle_cache.sh b/.gitlab/gradle-cache/mitigate_corrupted_gradle_cache.sh
new file mode 100755
index 00000000000..0088f9769b2
--- /dev/null
+++ b/.gitlab/gradle-cache/mitigate_corrupted_gradle_cache.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+
+set -uo pipefail
+
+gradle_version="${1:?usage: mitigate_corrupted_gradle_cache.sh }"
+script_dir="$(cd "$(dirname "$0")" && pwd)"
+
+java_home="${JAVA_25_HOME:-}"
+java_bin="${java_home:+$java_home/bin/}java"
+javac_bin="${java_home:+$java_home/bin/}javac"
+
+gradle_lib=""
+for gradle_home in "${GRADLE_USER_HOME:-$PWD/.gradle}" "$HOME/.gradle"; do
+ [[ -d "$gradle_home/wrapper/dists" ]] || continue
+ gradle_lib="$(
+ find "$gradle_home/wrapper/dists" -path "*/gradle-${gradle_version}/lib" -type d -print -quit \
+ 2>/dev/null
+ )"
+ [[ -n "$gradle_lib" ]] && break
+done
+if [[ -z "$gradle_lib" ]]; then
+ echo "Gradle $gradle_version distribution not found; leaving cache unchanged." >&2
+ exit 0
+fi
+
+build_dir="$(mktemp -d)"
+trap 'rm -rf "$build_dir"' EXIT
+
+# -proc:none keeps the compiler from running annotation processors bundled in the Gradle jars.
+if ! "$javac_bin" -proc:none -classpath "$gradle_lib/*" -d "$build_dir" \
+ "$script_dir/CorruptedGradleCacheMitigator.java"; then
+ echo "Could not compile CorruptedGradleCacheMitigator; leaving cache unchanged." >&2
+ exit 0
+fi
+
+"$java_bin" -classpath "$build_dir:$gradle_lib/*" \
+ CorruptedGradleCacheMitigator "$gradle_version"