From 4f6e64e65c78f54d57ea2bdfba14b2b7259a2e27 Mon Sep 17 00:00:00 2001 From: sora <2075279110@qq.com> Date: Thu, 17 Sep 2026 02:49:57 +0000 Subject: [PATCH] Stream docker pull progress; never rmi after a failed retag Two pull UX/correctness fixes: (1) a multi-GB pull with captured output is minutes of silence reading as a hang -- layer progress now streams (throttled) to stderr; (2) the mirror-tag -> canonical retag could fail silently and the following rmi then deleted the ONLY tag, losing a 342s pull and forcing a full re-download -- retag is now verified and the mirror tag kept on failure. Co-Authored-By: Claude --- evalharness/sandbox/prefetch.py | 35 +++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/evalharness/sandbox/prefetch.py b/evalharness/sandbox/prefetch.py index f0d31c3..aa2b081 100644 --- a/evalharness/sandbox/prefetch.py +++ b/evalharness/sandbox/prefetch.py @@ -109,14 +109,41 @@ def _pull_one(image: str, verbose: bool = True) -> None: print(f'· pulling sandbox image {image} via {src} ...', file=_sys.stderr, flush=True) t0 = _time.monotonic() - r = subprocess.run(['docker', 'pull', ref], capture_output=True, text=True, - timeout=3600) + # stream progress: a multi-GB pull with captured output is minutes + # of silence that reads as a hang. Non-TTY docker pull emits clean + # per-layer lines; forward the informative ones (throttled). + proc = subprocess.Popen(['docker', 'pull', ref], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, bufsize=1) + last_line = '' + last_show = 0.0 + import os as _os + for line in proc.stdout: + last_line = line.strip() + now = _time.monotonic() + if verbose and (any(k in line for k in + ('Pulling from', 'Pull complete', 'Status', + 'error', 'denied')) + or now - last_show > 10): + print(f' {last_line[:100]}', file=_sys.stderr, flush=True) + last_show = now + proc.wait(timeout=3600) + r = subprocess.CompletedProcess(proc.args, proc.returncode, + stdout='', stderr=last_line) if r.returncode == 0 and verbose: print(f'✓ image ready in {_time.monotonic() - t0:.0f}s', file=_sys.stderr, flush=True) if r.returncode == 0: - if ref != image: # retag the mirrored pull to the canonical name - subprocess.run(['docker', 'tag', ref, image], check=False) + if ref != image: + # retag BEFORE rmi and VERIFY: a silent tag failure followed + # by rmi deleted the only tag and lost a 342s multi-GB pull + tag = subprocess.run(['docker', 'tag', ref, image], + capture_output=True, text=True) + if tag.returncode != 0: + print(f'!! docker tag {ref} -> {image} failed: ' + f'{(tag.stderr or "")[:120]} (keeping the mirror tag)', + file=_sys.stderr, flush=True) + return # do NOT rmi: the mirror tag is the only handle subprocess.run(['docker', 'rmi', ref], check=False) return last_err = (ref, (r.stderr or '').strip()[:150])