From 97da4774244f7b10b41e18e05ccc72dc9e50c7fa Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:02:50 +0200 Subject: [PATCH] ci: retry dagger pipeline on transient registry/image-pull failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dagger job failed pulling python:3.11-bookworm from Docker Hub with a 520 (transient auth/CDN outage) — the residual risk noted in the Phase 3 dependency audit. The earlier hardening pre-pulled only the dagger engine image (registry.dagger.io); the base-image pull happens inside the engine via client.container().from_(...), which the host pre-pull cannot cover. Wrap the whole dagger pipeline (_run_checks) in a 3-attempt retry that re-runs on transient registry/pull markers (Docker Hub 5xx/429, oauth/authorize failures, TransportQueryError, i/o timeout, connection reset, context deadline). Permanent failures (lint/type/test) still fail immediately on the first attempt. Co-Authored-By: Claude Opus 4.8 --- dagger/ci.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/dagger/ci.py b/dagger/ci.py index 6a66141..77d24bb 100644 --- a/dagger/ci.py +++ b/dagger/ci.py @@ -6,8 +6,25 @@ import anyio import dagger +# Substrings that mark a transient registry / image-pull failure (e.g. a Docker +# Hub 5xx on the python:3.11-bookworm pull, or a registry.dagger.io timeout) that +# is worth retrying rather than failing the whole pipeline. +_TRANSIENT_MARKERS = ( + "pull image", + "failed to authorize", + "failed to fetch oauth", + "TransportQueryError", + "i/o timeout", + "connection reset", + "context deadline exceeded", + " 502", + " 503", + " 520", + " 429", +) -async def main() -> None: + +async def _run_checks() -> None: config = dagger.Config(log_output=sys.stderr) async with dagger.Connection(config) as client: source = client.host().directory( @@ -39,5 +56,21 @@ async def main() -> None: await checks.sync() +async def main() -> None: + attempts = 3 + for attempt in range(1, attempts + 1): + try: + await _run_checks() + return + except Exception as exc: # noqa: BLE001 — retry only transient registry/pull failures + message = str(exc) + transient = any(marker in message for marker in _TRANSIENT_MARKERS) + if attempt < attempts and transient: + print(f"dagger: transient registry/pull failure on attempt {attempt}; retrying in 20s", file=sys.stderr) + await anyio.sleep(20) + continue + raise + + if __name__ == "__main__": anyio.run(main)