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 <noreply@anthropic.com>
This commit is contained in:
sora 2026-09-17 02:49:57 +00:00
parent f4f10649df
commit 4f6e64e65c

View File

@ -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])