Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1499,9 +1499,24 @@ jobs:
with:
node-version: '24.21.0' # exact: bump deliberately (#1645)

- name: Setup Python
- name: Setup Python (attempt 1)
# setup-python downloads 3.12.10 on runners that don't cache it,
# which takes ~6 s. On 2026-10-28 (run 40771482790) the manifest
# fetch died on `getaddrinfo EAI_AGAIN raw.githubusercontent.com`
# and the step held the leg for 19 min before failing it. Cap the
# download and try once more; a second failure is not a blip.
id: setup-python
if: matrix.suite == 'e2e_pypi' || matrix.uv != '' || matrix.poetry != '' || matrix.pdm != '' || matrix.hatch != ''
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
timeout-minutes: 4
continue-on-error: true
with:
python-version: '3.12.10' # exact: last 3.12 with Windows+macOS builds; bump deliberately (#1645)

- name: Setup Python (attempt 2)
if: (matrix.suite == 'e2e_pypi' || matrix.uv != '' || matrix.poetry != '' || matrix.pdm != '' || matrix.hatch != '') && steps.setup-python.outcome != 'success'
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
timeout-minutes: 4
with:
python-version: '3.12.10' # exact: last 3.12 with Windows+macOS builds; bump deliberately (#1645)

Expand Down Expand Up @@ -2790,9 +2805,20 @@ jobs:
bun --version
node --no-warnings "$js" --version

- name: Setup Python + uv
- name: Setup Python (attempt 1)
# Capped and retried once, as in the e2e job.
id: setup-python
if: steps.gate.outputs.run == 'true'
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
timeout-minutes: 4
continue-on-error: true
with:
python-version: '3.12.10' # exact: last 3.12 with Windows+macOS builds; bump deliberately (#1645)

- name: Setup Python (attempt 2)
if: (steps.gate.outputs.run == 'true') && steps.setup-python.outcome != 'success'
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
timeout-minutes: 4
with:
python-version: '3.12.10' # exact: last 3.12 with Windows+macOS builds; bump deliberately (#1645)

Expand Down
23 changes: 18 additions & 5 deletions scripts/tests/test_merge_queue_fail_fast.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,29 @@ def test_no_job_tolerates_failure(self):
# A job-level continue-on-error would let ci-ok pass despite a failed
# job, and this script would then cancel a run that could still land.
# A step-level one hides that step's failure the same way, so only
# the capped cache restores may carry it: a miss there just means
# building cold.
# the capped cache restores may carry it (a miss there just means
# building cold), plus a capped first attempt that the very next
# step retries without tolerance.
self.assertIsNone(re.search(r"^ continue-on-error:", self.text, re.M))
steps = re.split(r"(?m)^ - ", self.text)
tolerant = [s for s in steps if re.search(r"(?m)^\s*continue-on-error:", s)]
tolerant = [i for i, s in enumerate(steps) if re.search(r"(?m)^\s*continue-on-error:", s)]
self.assertTrue(tolerant)
for step in tolerant:
self.assertRegex(step, r"(?m)^ uses: (Swatinem/rust-cache|actions/cache/restore)@", step[:80])
for i in tolerant:
step = steps[i]
self.assertRegex(step, r"(?m)^ timeout-minutes: \d+$", step[:80])
self.assertRegex(step, r"(?m)^ continue-on-error: true$", step[:80])
if re.search(r"(?m)^ uses: (Swatinem/rust-cache|actions/cache/restore)@", step):
continue
self.assertRegex(step, r"^name: .* \(attempt 1\)\n", step[:80])
sid = re.search(r"(?m)^ id: (\S+)$", step).group(1)
retry = steps[i + 1]
self.assertRegex(retry, r"^name: .* \(attempt 2\)\n", retry[:80])
self.assertIn(f"steps.{sid}.outcome != 'success'", retry, retry[:80])
self.assertEqual(
re.search(r"(?m)^ uses: .*$", step).group(0),
re.search(r"(?m)^ uses: .*$", retry).group(0),
)
self.assertNotRegex(retry, r"(?m)^\s*continue-on-error:", retry[:80])

def test_every_cache_restore_is_capped(self):
# An uncapped restore that hangs holds the queue head until the job
Expand Down
Loading