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
50 changes: 50 additions & 0 deletions .github/actions/retry/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Retry a flaky command
description: >-
Run a shell command, retrying on non-zero exit. For dependency installs
(npm ci, uv sync) whose only failures are transient network/toolchain
flakes — a node-gyp header fetch, a registry blip — so CI self-heals
instead of needing a manual re-run.

inputs:
command:
description: Shell command to run (and retry).
required: true
attempts:
description: Max attempts before giving up.
default: "3"
delay:
description: Seconds to wait between attempts.
default: "10"
working-directory:
description: Directory to run in.
default: "."

runs:
using: composite
steps:
- shell: bash
working-directory: ${{ inputs.working-directory }}
# command goes through env, never interpolated into the script body, so
# a command with quotes/specials can't break or inject into the runner.
env:
_CMD: ${{ inputs.command }}
_ATTEMPTS: ${{ inputs.attempts }}
_DELAY: ${{ inputs.delay }}
run: |
set -uo pipefail
n=0
while :; do
n=$((n + 1))
echo "::group::attempt $n/$_ATTEMPTS: $_CMD"
if bash -c "$_CMD"; then
echo "::endgroup::"
exit 0
fi
echo "::endgroup::"
if [ "$n" -ge "$_ATTEMPTS" ]; then
echo "::error::failed after $n attempts: $_CMD"
exit 1
fi
echo "::warning::attempt $n failed; retrying in ${_DELAY}s: $_CMD"
sleep "$_DELAY"
done
10 changes: 7 additions & 3 deletions .github/workflows/docs-site-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ jobs:

- name: Install website dependencies
if: steps.changes.outputs.site == 'true'
run: npm ci
working-directory: website
uses: ./.github/actions/retry
with:
command: npm ci
working-directory: website

- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
if: steps.changes.outputs.site == 'true'
Expand All @@ -46,7 +48,9 @@ jobs:

- name: Install ascii-guard
if: steps.changes.outputs.site == 'true'
run: python -m pip install ascii-guard==2.3.0 pyyaml==6.0.3
uses: ./.github/actions/retry
with:
command: python -m pip install ascii-guard==2.3.0 pyyaml==6.0.3

- name: Extract skill metadata for dashboard
if: steps.changes.outputs.site == 'true'
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ jobs:

- name: Install ruff + ty
if: steps.changes.outputs.python == 'true'
run: |
uv tool install ruff
uv tool install ty
uses: ./.github/actions/retry
with:
command: uv tool install ruff && uv tool install ty

- name: Determine base ref
id: base
Expand Down Expand Up @@ -194,7 +194,9 @@ jobs:

- name: Install ruff
if: steps.changes.outputs.python == 'true'
run: uv tool install ruff
uses: ./.github/actions/retry
with:
command: uv tool install ruff

- name: ruff check .
if: steps.changes.outputs.python == 'true'
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
RG_VERSION=15.1.0
RG_SHA256=1c9297be4a084eea7ecaedf93eb03d058d6faae29bbc57ecdaf5063921491599
RG_TARBALL=ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl.tar.gz
curl -sSfL -o "$RG_TARBALL" \
curl -sSfL --retry 3 --retry-delay 5 -o "$RG_TARBALL" \
"https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${RG_TARBALL}"
echo "${RG_SHA256} ${RG_TARBALL}" | sha256sum -c -
tar -xzf "$RG_TARBALL"
Expand Down Expand Up @@ -92,7 +92,9 @@ jobs:
# fails if the lock is out of sync with pyproject.toml), giving a
# reproducible env. It also creates .venv itself, so no separate
# `uv venv` step is needed.
run: uv sync --locked --python 3.11 --extra all --extra dev
uses: ./.github/actions/retry
with:
command: uv sync --locked --python 3.11 --extra all --extra dev

- name: Minimize uv cache
if: steps.changes.outputs.python == 'true'
Expand Down Expand Up @@ -195,7 +197,7 @@ jobs:
RG_VERSION=15.1.0
RG_SHA256=1c9297be4a084eea7ecaedf93eb03d058d6faae29bbc57ecdaf5063921491599
RG_TARBALL=ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl.tar.gz
curl -sSfL -o "$RG_TARBALL" \
curl -sSfL --retry 3 --retry-delay 5 -o "$RG_TARBALL" \
"https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${RG_TARBALL}"
echo "${RG_SHA256} ${RG_TARBALL}" | sha256sum -c -
tar -xzf "$RG_TARBALL"
Expand Down Expand Up @@ -227,7 +229,9 @@ jobs:
# fails if the lock is out of sync with pyproject.toml), giving a
# reproducible env. It also creates .venv itself, so no separate
# `uv venv` step is needed.
run: uv sync --locked --python 3.11 --extra all --extra dev
uses: ./.github/actions/retry
with:
command: uv sync --locked --python 3.11 --extra all --extra dev

- name: Minimize uv cache
if: steps.changes.outputs.python == 'true'
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ jobs:
with:
node-version: 22
cache: npm
# --ignore-scripts: typecheck only needs the TS sources + type defs, not
# native builds. Skipping install scripts drops node-pty's node-gyp
# header fetch — the transient flake that killed this job pre-`tsc` — and
# is faster. retry covers the remaining registry blips.
- if: steps.changes.outputs.frontend == 'true'
run: npm ci
uses: ./.github/actions/retry
with:
command: npm ci --ignore-scripts
- if: steps.changes.outputs.frontend == 'true'
run: npm run --prefix ${{ matrix.package }} typecheck

Expand All @@ -56,7 +62,11 @@ jobs:
with:
node-version: 22
cache: npm
# Keep install scripts here: the production build may need node-pty's
# native binary. retry handles the transient install-time fetch flakes.
- if: steps.changes.outputs.frontend == 'true'
run: npm ci
uses: ./.github/actions/retry
with:
command: npm ci
- if: steps.changes.outputs.frontend == 'true'
run: npm run --prefix apps/desktop build