From 1adb01d0b1b53e7b22f701c1106e472aacd18c44 Mon Sep 17 00:00:00 2001 From: Pascal Date: Tue, 23 Jun 2026 18:46:59 +0200 Subject: [PATCH 1/4] ci: pin actions to commit SHAs and add shellcheck Pin actions/github-script in catalog-assign.yml to a full commit SHA; all other workflows were already pinned. Add a repo-wide regression test that every workflow `uses:` ref is pinned to a 40-char commit SHA. Add a shellcheck job to lint.yml (--severity=error over scripts/bash/*.sh) and document the local command in CONTRIBUTING.md. --- .github/workflows/catalog-assign.yml | 2 +- .github/workflows/lint.yml | 12 ++++++++++ CONTRIBUTING.md | 8 +++++++ tests/test_github_workflows.py | 34 ++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/test_github_workflows.py diff --git a/.github/workflows/catalog-assign.yml b/.github/workflows/catalog-assign.yml index 78b4f552f3..f828794864 100644 --- a/.github/workflows/catalog-assign.yml +++ b/.github/workflows/catalog-assign.yml @@ -19,7 +19,7 @@ jobs: permissions: issues: write steps: - - uses: actions/github-script@v9 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | const issue = context.payload.issue; diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 59a02702a1..26d77a39b2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -42,3 +42,15 @@ jobs: globs: | '**/*.md' !extensions/**/*.md + + shellcheck: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + # shellcheck is preinstalled on ubuntu-latest runners. + # Start at --severity=error to block real bugs without flagging style + # (notably SC2155). Tighten in a follow-up after cleanup. + - name: Run shellcheck on scripts/bash + run: shellcheck --severity=error scripts/bash/*.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5cf5514a0a..97592cdda1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -113,6 +113,14 @@ uv pip install -e ".[test]" > `specify_cli` to this checkout's `src/`. This matches the gotcha documented in > `AGENTS.md` (Common Pitfalls). +#### Shell scripts + +```bash +shellcheck --severity=error scripts/bash/*.sh +``` + +The CI `lint.yml` `shellcheck` job blocks at `--severity=error` to catch real bugs while leaving stylistic warnings (SC2155 etc.) advisory. + ### Manual testing #### Testing setup diff --git a/tests/test_github_workflows.py b/tests/test_github_workflows.py new file mode 100644 index 0000000000..7ad0b714ec --- /dev/null +++ b/tests/test_github_workflows.py @@ -0,0 +1,34 @@ +"""Static checks for repository GitHub Actions workflows.""" + +from __future__ import annotations + +import re +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parent.parent +WORKFLOWS_DIR = REPO_ROOT / ".github" / "workflows" +# Match both the dedicated-step form (` uses: x@sha`) and the +# inline shorthand (` - uses: x@sha`) used in catalog-assign.yml. +USES_RE = re.compile(r"^\s*(?:-\s*)?uses:\s*(?P\S+)", re.MULTILINE) + + +def test_github_actions_are_pinned_to_full_commit_shas(): + unpinned_refs = [] + + workflows = sorted( + list(WORKFLOWS_DIR.glob("*.yml")) + list(WORKFLOWS_DIR.glob("*.yaml")) + ) + assert workflows + + for workflow in workflows: + workflow_text = workflow.read_text(encoding="utf-8") + for match in USES_RE.finditer(workflow_text): + uses_ref = match.group("ref") + if uses_ref.startswith(("./", "../")): + continue + if re.search(r"@[0-9a-f]{40}$", uses_ref): + continue + unpinned_refs.append(f"{workflow.relative_to(REPO_ROOT)}: {uses_ref}") + + assert unpinned_refs == [] From 5d5fb75ffd3b6825ada12993e38b0ca0cb1cdfe5 Mon Sep 17 00:00:00 2001 From: Pascal Date: Tue, 23 Jun 2026 21:10:15 +0200 Subject: [PATCH 2/4] ci: use repo-standard actions/checkout v7.0.0 in shellcheck job --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 26d77a39b2..4001457403 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # shellcheck is preinstalled on ubuntu-latest runners. # Start at --severity=error to block real bugs without flagging style From b5e0147717e0f6df68122ddef342c58c90815bfe Mon Sep 17 00:00:00 2001 From: Pascal Date: Tue, 23 Jun 2026 22:57:08 +0200 Subject: [PATCH 3/4] ci: shellcheck all tracked shell scripts Assisted-by: Codex (model: GPT-5, autonomous) --- .github/workflows/lint.yml | 4 ++-- CONTRIBUTING.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4001457403..84074b4791 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -52,5 +52,5 @@ jobs: # shellcheck is preinstalled on ubuntu-latest runners. # Start at --severity=error to block real bugs without flagging style # (notably SC2155). Tighten in a follow-up after cleanup. - - name: Run shellcheck on scripts/bash - run: shellcheck --severity=error scripts/bash/*.sh + - name: Run shellcheck on shell scripts + run: git ls-files -z -- '*.sh' | xargs -0 shellcheck --severity=error diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97592cdda1..d5cee8d9ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,7 +116,7 @@ uv pip install -e ".[test]" #### Shell scripts ```bash -shellcheck --severity=error scripts/bash/*.sh +git ls-files -z -- '*.sh' | xargs -0 shellcheck --severity=error ``` The CI `lint.yml` `shellcheck` job blocks at `--severity=error` to catch real bugs while leaving stylistic warnings (SC2155 etc.) advisory. From b5190f3dc957edf0a25b159dad02479d579b31a5 Mon Sep 17 00:00:00 2001 From: Pascal Date: Wed, 24 Jun 2026 15:16:47 +0200 Subject: [PATCH 4/4] ci: address workflow hygiene review feedback Assisted-by: Codex (model: GPT-5, autonomous) --- CONTRIBUTING.md | 4 +++- tests/test_github_workflows.py | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5cee8d9ba..7cc6d28f86 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -119,7 +119,9 @@ uv pip install -e ".[test]" git ls-files -z -- '*.sh' | xargs -0 shellcheck --severity=error ``` -The CI `lint.yml` `shellcheck` job blocks at `--severity=error` to catch real bugs while leaving stylistic warnings (SC2155 etc.) advisory. +The CI `lint.yml` `shellcheck` job currently reports and blocks only +error-severity findings. Warnings such as SC2155 are intentionally outside this +job until a follow-up cleanup tightens the threshold. ### Manual testing diff --git a/tests/test_github_workflows.py b/tests/test_github_workflows.py index 7ad0b714ec..b6ee409fb0 100644 --- a/tests/test_github_workflows.py +++ b/tests/test_github_workflows.py @@ -11,6 +11,7 @@ # Match both the dedicated-step form (` uses: x@sha`) and the # inline shorthand (` - uses: x@sha`) used in catalog-assign.yml. USES_RE = re.compile(r"^\s*(?:-\s*)?uses:\s*(?P\S+)", re.MULTILINE) +PINNED_SHA_RE = re.compile(r"@[0-9a-f]{40}$", re.IGNORECASE) def test_github_actions_are_pinned_to_full_commit_shas(): @@ -27,8 +28,14 @@ def test_github_actions_are_pinned_to_full_commit_shas(): uses_ref = match.group("ref") if uses_ref.startswith(("./", "../")): continue - if re.search(r"@[0-9a-f]{40}$", uses_ref): + if PINNED_SHA_RE.search(uses_ref): continue unpinned_refs.append(f"{workflow.relative_to(REPO_ROOT)}: {uses_ref}") assert unpinned_refs == [] + + +def test_pinned_action_ref_accepts_uppercase_hex_sha(): + assert PINNED_SHA_RE.search( + "actions/example@0123456789ABCDEF0123456789ABCDEF01234567" + )