From ff17d0e7b818397a93a3ed02805b8ca935e5ce60 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 12:46:51 +0000 Subject: [PATCH 1/3] Bound the sbt warm-cache update in Dockerfile.sbt The warm `sbt update` loop had no deadline. On 2026-10-22 a stalled resolver connection (Ivy has no read timeout) hung the layer 4 times in 4 hours, each until the job's 45-minute timeout (#1665), with no diagnostic. Give each warm update a 600 s deadline (timeout -k 30) and one retry, and fail the layer if both attempts fail. timeout(1) signals its whole process group, so the JVM under the sbt script dies too and leaves no Ivy lock for the retry. This also fixes a silent failure: the loop sits inside a `set -eu && ...` AND-list, where errexit does not apply, so a failed update was ignored and the image shipped unwarmed. The new loop exits on failure explicitly. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_011f9aePV6aRxWWuVgK81qyD --- tests/docker/Dockerfile.sbt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/docker/Dockerfile.sbt b/tests/docker/Dockerfile.sbt index 99509ea52..7906e3180 100644 --- a/tests/docker/Dockerfile.sbt +++ b/tests/docker/Dockerfile.sbt @@ -97,6 +97,11 @@ RUN set -eu \ # 1.9.9) boot quickly cold and stay out: the scala-cli real-tool test serves # the image's Central cache from memory, which more lines would bloat past # its 2g container. +# Each warm `update` gets a 600 s deadline and one retry (normally it takes +# well under a minute): Ivy has no read timeout, so one stalled resolver +# connection used to hang the layer until the job's 45-min timeout (#1665). +# timeout(1) signals its whole process group, so the sbt script's JVM dies +# with it and leaves no Ivy lock behind for the retry. ARG SBT_WARM_VERSIONS="0.13.18 1.2.8 1.13.0 2.0.9" # CI's blocking slice uses only two sbt lines; nightly/compat keep all tools. ARG SBT_WARM_TOOLS=1 @@ -108,8 +113,15 @@ RUN set -eu \ 'libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.11"' > "$d/build.sbt"; \ case "$v" in 0.*|1.[0-3].*) jdk=/opt/jdk8;; *) jdk=/opt/jdk17;; esac; \ case "$v" in 2.*) extra=--server;; *) extra=;; esac; \ - (cd "$d" && JAVA_HOME="$jdk" PATH="$jdk/bin:$PATH" \ - sbt -batch -no-colors -Dsbt.server.autostart=false $extra update); \ + ok=; for attempt in 1 2; do \ + if (cd "$d" && JAVA_HOME="$jdk" PATH="$jdk/bin:$PATH" \ + timeout -k 30 600 \ + sbt -batch -no-colors -Dsbt.server.autostart=false $extra update); then \ + ok=1; break; \ + fi; \ + echo "warm sbt $v update failed or ran past 600 s (attempt $attempt)" >&2; \ + done; \ + [ -n "$ok" ]; \ rm -rf "$d"; \ done \ && if [ "$SBT_WARM_TOOLS" = 1 ]; then \ From 9758d4f942c482942016f2e3eccf7e72576f73b6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 05:35:48 +0000 Subject: [PATCH 2/3] Pin unearth<0.19 when bootstrapping PDM 2.x unearth 0.19.0 (published 2026-10-27 03:05 UTC) moved to httpx2. PDM 2.x declares an unbounded unearth, so a fresh bootstrap now resolves 0.19 and every `pdm lock` fails with "PDMPyPIClient is not an instance or subtype of type (Client)". That turns the required `e2e_vex_build` pdm leg red on every PR and in the merge queue, before any socket-patch code runs. Pin unearth<0.19 for PDM 2.x in the e2e bootstrap and in scripts/backtest-pdm.py, which resolves the 0.18 line these releases ran with until today. Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-cli/tests/e2e_vex_build/pdm.rs | 6 ++++++ scripts/backtest-pdm.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/crates/socket-patch-cli/tests/e2e_vex_build/pdm.rs b/crates/socket-patch-cli/tests/e2e_vex_build/pdm.rs index 7331301bd..16efed366 100644 --- a/crates/socket-patch-cli/tests/e2e_vex_build/pdm.rs +++ b/crates/socket-patch-cli/tests/e2e_vex_build/pdm.rs @@ -137,6 +137,12 @@ fn pins_for(v: &str) -> Vec { if ((2, 21, 0)..(2, 26, 1)).contains(&t) { pins.push("hishel<1"); } + // 2.x declares an unbounded `unearth`, and unearth 0.19 (2026-10-27) + // moved to `httpx2`: PDM's PyPI client is no longer the `httpx` + // client unearth expects, so `pdm lock` dies with "PDMPyPIClient is + // not an instance or subtype of type (Client)" (seen on 2.29.2). + // `<0.19` resolves the 0.18 line every 2.x ran with before. + pins.push("unearth<0.19"); } let mut out = vec![format!("pdm=={v}")]; out.extend(pins.into_iter().map(String::from)); diff --git a/scripts/backtest-pdm.py b/scripts/backtest-pdm.py index e7f4db789..d403cb545 100644 --- a/scripts/backtest-pdm.py +++ b/scripts/backtest-pdm.py @@ -204,6 +204,11 @@ def pins_for(version): # ModuleNotFoundError before locking. if (2, 21) <= v < (2, 26, 1): pins.append("hishel<1") + # 2.x declares an unbounded `unearth`; 0.19 (2026-10-27) moved to + # httpx2 and breaks PDM's PyPI client ("PDMPyPIClient is not an + # instance or subtype of type (Client)" on `pdm lock`, seen on + # 2.29.2). `<0.19` resolves the 0.18 line 2.x ran with before. + pins.append("unearth<0.19") return pins From b9e018c7003bfaea114bf517bafe099ed72c8e33 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 12:59:53 +0000 Subject: [PATCH 3/3] Fail the sbt warm layer when both attempts fail The `[ -n "$ok" ]` check was a no-op: errexit does not apply inside the for loop, which is a non-final operand of the RUN's `&&` list, so the loop still exited 0 after two failed attempts. Exit explicitly. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_011f9aePV6aRxWWuVgK81qyD --- tests/docker/Dockerfile.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/docker/Dockerfile.sbt b/tests/docker/Dockerfile.sbt index 7906e3180..a7558ce8b 100644 --- a/tests/docker/Dockerfile.sbt +++ b/tests/docker/Dockerfile.sbt @@ -121,7 +121,7 @@ RUN set -eu \ fi; \ echo "warm sbt $v update failed or ran past 600 s (attempt $attempt)" >&2; \ done; \ - [ -n "$ok" ]; \ + [ -n "$ok" ] || exit 1; \ rm -rf "$d"; \ done \ && if [ "$SBT_WARM_TOOLS" = 1 ]; then \