Skip to main content

max / quasi-type

Retry a fetch, because four of them per image build earn a 429 Measured today, and it is a live build failure rather than a hypothetical: the Alloy image cuts two slots and shop's build script cuts a third face into its own cache, so one image build asks raw.githubusercontent.com for the pinned bases four times. That is enough to be rate-limited from a developer's own laptop, and with no retry the build died with the pinned bytes one second away. curl retries 408, 429 and the 5xx family on `--retry` by itself; `--retry-all-errors` adds the connection failures, which are the other half of what a shared network does to a build. Five attempts, two seconds apart. This bounds the damage and does not fix the cause. The cause is that four fetches of the same two files leave the machine at all, and the answer to that is a mirror on infrastructure we own - the same conversation as mirroring the Fedora base images (GO alloy ebf30337; the fragility is filed as alloy 3d41d15a). The error message now says so, so the next person to hit it does not add a sixth retry.
Author: Max Johnson <me@maxj.phd> · 2026-08-17 15:45 UTC
Signed with PGP, not checked
Commit: cbbb9e3dd6faa29582b9437c0685ff91af97afdb
Parent: 6d076b7
1 file changed, +26 insertions, -1 deletion
M src/base.rs +26 -1
@@ -615,6 +615,22 @@
615 615 /// be the largest thing in the dependency tree and would drag in the crypto
616 616 /// provider question for no gain: the integrity guarantee here is the sha256
617 617 /// below, not the transport.
618 + ///
619 + /// **It retries, and that is not defensive coding.** The pins point at
620 + /// raw.githubusercontent.com, which rate-limits by IP, and a machine that builds
621 + /// Alloy asks it four times in one build: the image cuts two slots, and shop's
622 + /// build script cuts a third face into its own cache. Measured 2026-08-17, that
623 + /// is enough to earn a 429 on a developer's own laptop, and without a retry the
624 + /// image build fails on somebody else's rate limiter with the pinned bytes
625 + /// sitting one second away. curl retries 408, 429 and the 5xx family on
626 + /// `--retry` by itself; `--retry-all-errors` adds the connection failures, which
627 + /// are the other half of what a shared network does to a build.
628 + ///
629 + /// This bounds the damage rather than fixing the cause. The cause is that four
630 + /// fetches of the same two files leave the machine, and the real answer is a
631 + /// mirror on infrastructure we own — the same conversation as mirroring the
632 + /// Fedora base images (GO alloy `ebf30337`, and the fragility itself is alloy
633 + /// `3d41d15a`).
618 634 fn fetch(url: &str, dest: &Path) -> Result<(), Error> {
619 635 if let Some(parent) = dest.parent() {
620 636 std::fs::create_dir_all(parent).map_err(|e| Error::Io(parent.to_path_buf(), e))?;
@@ -626,6 +642,11 @@
626 642 "--location",
627 643 "--silent",
628 644 "--show-error",
645 + "--retry",
646 + "5",
647 + "--retry-delay",
648 + "2",
649 + "--retry-all-errors",
629 650 "--output",
630 651 ])
631 652 .arg(&partial)
@@ -634,7 +655,11 @@
634 655 .map_err(|e| Error::Fetch(format!("could not run curl: {e}")))?;
635 656 if !status.success() {
636 657 let _ = std::fs::remove_file(&partial);
637 - return Err(Error::Fetch(format!("curl failed on {url} ({status})")));
658 + return Err(Error::Fetch(format!(
659 + "curl failed on {url} ({status}), after retrying. A 429 here is this \
660 + machine's IP being rate-limited by the host the base is pinned at, and \
661 + the fix is a mirror rather than another retry."
662 + )));
638 663 }
639 664 std::fs::rename(&partial, dest).map_err(|e| Error::Io(dest.to_path_buf(), e))?;
640 665 Ok(())