Skip to content

16 unretried curl | sh rustup installs across 12 workflows — no retry, and no pipefail, so a truncated installer can pass the stage #289

Description

@avrabe

[fathom (gale)] — root-caused from a real red on #285, which only bumps a tool pin and had no business failing a Zephyr kernel test.

The failure

msgq_usage (qemu_cortex_m3) failed in step 4, "Install Rust + ARM target" — before any gale code ran:

curl: (35) Recv failure: Connection reset by peer
error: rustup could not choose a version of rustup to run, because one wasn't
       specified explicitly, and no default is configured.
##[error]Process completed with exit code 1

The curl https://sh.rustup.rs | sh was reset at the network level, so no default toolchain was installed, and the next line (rustup target add thumbv7m-none-eabi) failed. External cause, but a fragility we own.

Two distinct problems

1. No retry, on a step that runs dozens of times per PR.

grep -rn "sh.rustup.rs" .github/workflows/*.yml   ->  16 sites across 12 workflows

zephyr-tests.yml alone has four, and they feed the big matrices (~45 qemu_cortex_m3 jobs, mps2/an385, the SMP set). A single PR therefore performs dozens of unretried network installs; with several PRs open the expected number of resets stops being small. I noticed this precisely because I had four PRs in flight — my own queue pressure made a latent fragility visible.

2. curl … | sh with no pipefail can pass a TRUNCATED install.

The step has no shell: override, so it runs under Actions' default bash -e {0} — which sets -e but not -o pipefail. In curl -sSf … | sh -s -- -y, the pipeline's exit status is sh's. If curl dies mid-transfer, sh executes a partial installer and can exit 0.

This time the truncation was caught by the following line failing. That was luck, not design: a partial install that happens to leave a usable rustup shim would pass the stage and fail later somewhere far less legible — or not at all.

Proposed fix

Split download from execution, and let curl do the retrying:

- name: Install Rust + ARM target
  run: |
    set -euo pipefail
    apt-get update -qq && apt-get install -y -qq curl > /dev/null 2>&1
    curl --proto '=https' --tlsv1.2 -sSf \
         --retry 5 --retry-all-errors --retry-delay 2 --connect-timeout 15 \
         https://sh.rustup.rs -o /tmp/rustup-init.sh
    sh /tmp/rustup-init.sh -y --default-toolchain stable
    . /root/.cargo/env
    rustup target add thumbv7m-none-eabi

Three changes, each doing one thing:

  • --retry 5 --retry-all-errors--retry alone does not cover (35) Recv failure; --retry-all-errors is the flag that makes connection resets retryable. This is the actual fix for the observed failure.
  • -o then sh — a truncated download can no longer be executed, because curl's non-zero exit stops the step before sh runs. This is the fix for the silent variant.
  • set -euo pipefail — belt and braces; with -o file the pipe is gone anyway.

Kill-criterion

Point the URL at a host that resets the connection (or block it) and confirm the step fails at the download, not three lines later — and that no rustup-init.sh is executed. A retry that has never been shown to catch anything is not a retry.

Scope / honesty

  • This is an infrastructure failure with an identified mechanism, not a "flaky test". I am not proposing to re-run and move on; the re-run is to unblock chore(synth): bump the pin 0.52.0 -> 0.57.0 — the isolation core goes 8/9 -> 9/9 #285, the fix is this issue.
  • I have not proven the silent-truncation path has ever actually occurred here. The (35) reset is observed; the missing-pipefail hazard is read off the step definition and Actions' documented default shell. Recorded as a latent hazard, not an incident.
  • Deliberately not landing this yet: four PRs are already saturating the runners, and a 17-site workflow change would add another full matrix. It should land when the queue drains.
  • Related but separate: these steps set RUSTUP_HOME: /mnt/.rustup while sourcing /root/.cargo/env — the /root disk-exhaustion workaround. Not touched here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions