From 3f3daba974c5d4d067268299e8c864c45e8f64a8 Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Fri, 25 Sep 2026 13:45:40 +0200 Subject: [PATCH 01/10] feat!: make governance workflows reusable Consumer repos call update-action-pins, conventional-commits, osv-scanner and the issue automations from a small stub per workflow instead of carrying drifting copies. See "Governance workflows" in the README for the stubs and the new check names. - update-action-pins: quote- and spacing-tolerant matcher, scans all of .github/ (composite actions too), no paths filter; covered by scripts/test-update-action-pins.sh - osv-scanner: new, reusable only; scan-args is an input - pins bumped to the versions consumers already use BREAKING CHANGE: make repo-settings no longer installs update-action-pins.yml. Consumers call it through a stub instead. --- .github/workflows/conventional-commits.yml | 6 +- .github/workflows/issues-add-labels.yaml | 1 + .github/workflows/issues-add-to-project.yml | 6 +- .github/workflows/osv-scanner.yml | 43 +++++++ .github/workflows/test-actions.yml | 4 + .github/workflows/update-action-pins.yml | 26 +++-- README.md | 119 +++++++++++++++++++- common.mk | 1 - scripts/repo-settings.sh | 10 +- scripts/test-update-action-pins.sh | 44 ++++++++ 10 files changed, 238 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/osv-scanner.yml create mode 100755 scripts/test-update-action-pins.sh diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml index cb64826..c2a7a72 100644 --- a/.github/workflows/conventional-commits.yml +++ b/.github/workflows/conventional-commits.yml @@ -4,6 +4,7 @@ on: pull_request: branches: ["main"] types: [opened, edited, synchronize, reopened] + workflow_call: permissions: pull-requests: read @@ -13,7 +14,7 @@ jobs: name: PR Title runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@e32d7e603df1aa1ba07e981f2a23455dee596825 # v5 + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -33,9 +34,10 @@ jobs: name: Commit Messages runs-on: ubuntu-latest steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 + persist-credentials: false - uses: wagoid/commitlint-github-action@b948419dd99f3fd78a6548d48f94e3df7f6bf3ed # v6 with: configFile: .commitlintrc.yml diff --git a/.github/workflows/issues-add-labels.yaml b/.github/workflows/issues-add-labels.yaml index 7cf9551..87695d0 100644 --- a/.github/workflows/issues-add-labels.yaml +++ b/.github/workflows/issues-add-labels.yaml @@ -7,6 +7,7 @@ on: types: - reopened - opened + workflow_call: jobs: label_issues: runs-on: ubuntu-latest diff --git a/.github/workflows/issues-add-to-project.yml b/.github/workflows/issues-add-to-project.yml index 7509dd5..5417967 100644 --- a/.github/workflows/issues-add-to-project.yml +++ b/.github/workflows/issues-add-to-project.yml @@ -10,6 +10,10 @@ on: pull_request: types: - opened + workflow_call: + secrets: + ADD_TO_PROJECT_PAT: + required: true jobs: add-to-project: @@ -18,7 +22,7 @@ jobs: # Authenticates with ADD_TO_PROJECT_PAT, so the workflow GITHUB_TOKEN needs no scopes. permissions: {} steps: - - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 with: project-url: https://github.com/orgs/opendefensecloud/projects/3 github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml new file mode 100644 index 0000000..390ac9d --- /dev/null +++ b/.github/workflows/osv-scanner.yml @@ -0,0 +1,43 @@ +name: OSV-Scanner + +# Reusable only. The caller's stub carries the triggers (pull_request, +# merge_group, push to main, a weekly schedule); its event decides which job +# runs. See "Governance workflows" in the README. + +on: + workflow_call: + inputs: + scan-args: + description: Arguments passed to osv-scanner, one per line, e.g. add `--config ./.osv-scanner.toml`. + type: string + default: |- + --include-git-root + -r + ./ + +# Least privilege by default. The write permission needed to upload the SARIF +# report is granted per job, not here -- a top-level write permission is what +# OpenSSF Scorecard's Token-Permissions check flags. +permissions: + contents: read + +jobs: + scan-scheduled: + if: ${{ github.event_name == 'push' || github.event_name == 'schedule' }} + permissions: + # Required to upload SARIF file to CodeQL. See: https://github.com/github/codeql-action/issues/2117 + actions: read + security-events: write + contents: read + uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@a345acffa64b0eaede81a3d9aae6141214d9c8fc # v2.6.0 + with: + scan-args: ${{ inputs.scan-args }} + scan-pr: + if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} + permissions: + actions: read + security-events: write + contents: read + uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@a345acffa64b0eaede81a3d9aae6141214d9c8fc # v2.6.0 + with: + scan-args: ${{ inputs.scan-args }} diff --git a/.github/workflows/test-actions.yml b/.github/workflows/test-actions.yml index 936629a..023a2fe 100644 --- a/.github/workflows/test-actions.yml +++ b/.github/workflows/test-actions.yml @@ -5,11 +5,13 @@ on: paths: - '.github/actions/**/*.sh' - 'scripts/*.sh' + - '.github/workflows/update-action-pins.yml' push: branches: [main] paths: - '.github/actions/**/*.sh' - 'scripts/*.sh' + - '.github/workflows/update-action-pins.yml' permissions: contents: read @@ -31,3 +33,5 @@ jobs: run: .github/actions/diff-check/test-verify-clean.sh - name: Test extract-go-version.sh run: .github/actions/get-go-version/test-extract-go-version.sh + - name: Test the update-action-pins check + run: scripts/test-update-action-pins.sh diff --git a/.github/workflows/update-action-pins.yml b/.github/workflows/update-action-pins.yml index dcf48e2..9502548 100644 --- a/.github/workflows/update-action-pins.yml +++ b/.github/workflows/update-action-pins.yml @@ -1,31 +1,41 @@ name: Update Action Pins +# Reusable, and also run on dev-kit's own PRs. Consumers call it from a stub; +# see "Governance workflows" in the README. +# +# No `paths` filter on purpose: a required check gated by paths never reports on +# PRs outside them, so the merge stays blocked. + permissions: contents: read on: pull_request: - paths: - - ".github/workflows/**" - - ".github/actions/**" + workflow_call: jobs: check-pins: name: Check action pins runs-on: ubuntu-latest steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Verify all actions are pinned to a SHA run: | - unpinned=$(grep -rhE '^\s+(- )?uses: ' .github/ \ - | grep -vE "^\s+(- )?uses: ['\"]?\./" \ - | grep -vE '@[0-9a-f]{40}($|\s)' || true) + # Scan all of .github/ so composite actions are covered. Strip quotes + # before matching so single- and double-quoted values are both handled, + # and allow any YAML sequence spacing: `- uses:` is valid YAML, and a + # matcher expecting exactly one space would let an unpinned action + # through unseen. + unpinned=$(grep -rhE '^[[:space:]]*(-[[:space:]]+)?uses:' .github/ \ + | tr -d "\"'" \ + | grep -vE 'uses:[[:space:]]*\./' \ + | grep -vE '@[0-9a-f]{40}([[:space:]]|$)' || true) if [[ -n "$unpinned" ]]; then echo "::error::Found unpinned GitHub Actions (must use SHA digest, not tag):" echo "$unpinned" echo "" - echo "Run 'GITHUB_TOKEN=\$(gh auth token) update-action-pins .github/workflows/ .github/actions/' to fix." + echo "Run 'GITHUB_TOKEN=\$(gh auth token) update-action-pins .github/' to fix." exit 1 fi diff --git a/README.md b/README.md index b54e637..68bb856 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ The repository settings are configurable via make variables (set them in your `M | `REPO_REQUIRED_APPROVING_REVIEW_COUNT` | `1` | Number of approving reviews required to merge | | `REPO_REQUIRE_CODE_OWNER_REVIEW` | `false` | Require an approving review from code owners | | `REPO_REQUIRE_BRANCH_UP_TO_DATE` | `false` | Require branches to be up to date before merging (needs at least one `REPO_STATUS_CHECKS` value; `repo-settings` fails otherwise) | -| `REPO_STATUS_CHECKS` | `[]` | JSON array of status-check contexts that must pass (e.g. `["CI","Check action pins"]`); each job name is used as-is, so contexts with spaces work; the `required_status_checks` rule is only added when this is non-empty | +| `REPO_STATUS_CHECKS` | `[]` | JSON array of status-check contexts that must pass (e.g. `["CI","update-action-pins / Check action pins"]`); each job name is used as-is, so contexts with spaces work; the `required_status_checks` rule is only added when this is non-empty | | `REPO_RULESET_BRANCHES` | `[]` | JSON array of additional branch patterns the ruleset applies to (e.g. `["release/*"]`); short names are normalized to `refs/heads/...`; the default branch is always protected | ### GitHub Actions @@ -239,6 +239,123 @@ jobs: | `cachix-auth-token` | `''` | Auth token; empty falls back to a read-only cache, as on fork PRs | | `cachix-signing-key` | `''` | Signing key; empty disables pushing to the cache | +### Governance workflows + +The org-wide checks and automations are reusable workflows. A consuming repo +keeps one small stub per workflow, which sets the triggers and the permissions +and calls dev-kit. Pin each stub to a SHA like any other action; Renovate bumps +them together with `DEV_KIT_VERSION`. + +| Workflow | Stub triggers | Needs | +| --- | --- | --- | +| `update-action-pins.yml` | `pull_request` | | +| `conventional-commits.yml` | `pull_request` (opened, edited, synchronize, reopened) | `.commitlintrc.yml` in the repo | +| `osv-scanner.yml` | `pull_request`, `merge_group`, `push` to main, weekly | optional `scan-args` input | +| `issues-add-labels.yaml` | `issues` (opened, reopened) | | +| `issues-add-to-project.yml` | `issues`, `pull_request` (opened) | `ADD_TO_PROJECT_PAT` secret | +| `renovate-auto-approve.yml` | `pull_request` | see the header of the workflow | + +`.github/workflows/update-action-pins.yml`: + +```yaml +name: Update Action Pins +on: + pull_request: +permissions: + contents: read +jobs: + update-action-pins: + uses: opendefensecloud/dev-kit/.github/workflows/update-action-pins.yml@<40-char-sha> # +``` + +`.github/workflows/conventional-commits.yml`: + +```yaml +name: Conventional Commits +on: + pull_request: + branches: ["main"] + types: [opened, edited, synchronize, reopened] +permissions: + pull-requests: read +jobs: + conventional-commits: + uses: opendefensecloud/dev-kit/.github/workflows/conventional-commits.yml@<40-char-sha> # +``` + +`.github/workflows/osv-scanner.yml`: + +```yaml +name: OSV-Scanner +on: + pull_request: + branches: ["main"] + merge_group: + types: [checks_requested] + schedule: + - cron: "12 12 * * 1" + push: + branches: ["main"] +permissions: + contents: read +jobs: + osv-scanner: + permissions: + actions: read + security-events: write + contents: read + uses: opendefensecloud/dev-kit/.github/workflows/osv-scanner.yml@<40-char-sha> # + # with: + # scan-args: |- + # --include-git-root + # --config ./.osv-scanner.toml + # -r + # ./ +``` + +`.github/workflows/issues-add-labels.yaml`: + +```yaml +name: Label issues +on: + issues: + types: [opened, reopened] +permissions: + contents: read +jobs: + label: + permissions: + issues: write + uses: opendefensecloud/dev-kit/.github/workflows/issues-add-labels.yaml@<40-char-sha> # +``` + +`.github/workflows/issues-add-to-project.yml`: + +```yaml +name: Add issues to project +on: + issues: + types: [opened] + pull_request: + types: [opened] +permissions: + contents: read +jobs: + add-to-project: + permissions: {} + uses: opendefensecloud/dev-kit/.github/workflows/issues-add-to-project.yml@<40-char-sha> # + secrets: + ADD_TO_PROJECT_PAT: ${{ secrets.ADD_TO_PROJECT_PAT }} +``` + +A job from a reusable workflow reports as ` / `, so +`REPO_STATUS_CHECKS` needs the new names after switching to a stub, e.g. +`update-action-pins / Check action pins`, `conventional-commits / PR Title` and +`conventional-commits / Commit Messages`. osv-scanner nests Google's reusable +workflow one level deeper, so its scan reports as +`osv-scanner / scan-pr / osv-scan`. Update the list and rerun `make repo-settings` in the same change, or the old +names stay required and never report. + ### Default git hooks The dev shell installs the following git hooks automatically: diff --git a/common.mk b/common.mk index ed28f46..35ee683 100644 --- a/common.mk +++ b/common.mk @@ -96,7 +96,6 @@ repo-settings: ## Reconcile GitHub repository settings (labels, merge strategy, REPO_REQUIRE_LAST_PUSH_APPROVAL='$(REPO_REQUIRE_LAST_PUSH_APPROVAL)' \ REPO_RULESET_BRANCHES='$(REPO_RULESET_BRANCHES)' \ REPO_STATUS_CHECKS='$(REPO_STATUS_CHECKS)' \ - DEV_KIT_VERSION='$(DEV_KIT_VERSION)' \ GH='$(GH)' JQ='$(JQ)' \ bash diff --git a/scripts/repo-settings.sh b/scripts/repo-settings.sh index 681f8f5..c608cb6 100755 --- a/scripts/repo-settings.sh +++ b/scripts/repo-settings.sh @@ -3,7 +3,7 @@ set -euo pipefail # Reconcile a repository's GitHub settings: labels, merge strategy, secret -# scanning, branch protection ruleset, and the update-action-pins workflow. +# scanning and branch protection ruleset. # # All configuration is passed through environment variables. Defaults are the # single source of truth in common.mk and are always passed by the make target; @@ -18,7 +18,6 @@ set -euo pipefail # REPO_ALLOW_SQUASH_MERGE allow squash merging # REPO_ALLOW_REBASE_MERGE allow rebase merging # REPO_REQUIRE_LAST_PUSH_APPROVAL require the most recent push to be approved before merging -# DEV_KIT_VERSION dev-kit version to fetch the workflow from # GH, JQ commands used to talk to GitHub and build JSON "$GH" auth status > /dev/null 2>&1 || { @@ -158,11 +157,4 @@ else echo " Created new ruleset" fi -echo " Installing update-action-pins workflow..." -mkdir -p .github/workflows -curl --fail -sSL \ - "https://raw.githubusercontent.com/opendefensecloud/dev-kit/$DEV_KIT_VERSION/.github/workflows/update-action-pins.yml" \ - -o .github/workflows/update-action-pins.yml -echo " Wrote .github/workflows/update-action-pins.yml" - echo "Done." diff --git a/scripts/test-update-action-pins.sh b/scripts/test-update-action-pins.sh new file mode 100755 index 0000000..0bf0850 --- /dev/null +++ b/scripts/test-update-action-pins.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Runs the pin check from update-action-pins.yml itself (extracted with yq), so +# the test cannot drift from what CI runs. + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +CHECK="$(yq '.jobs.check-pins.steps[] | select(.name == "Verify all actions are pinned to a SHA") | .run' \ + "$ROOT/.github/workflows/update-action-pins.yml")" +[[ -n "$CHECK" ]] || { echo "FAIL: pin check step not found"; exit 1; } + +SHA=0123456789abcdef0123456789abcdef01234567 +fail=0 + +# expect +expect() { + local want=$1 desc=$2 file=$3 content=$4 dir got + dir="$(mktemp -d)" + mkdir -p "$dir/.github/$(dirname "$file")" + printf '%s\n' "$content" > "$dir/.github/$file" + if (cd "$dir" && bash -c "$CHECK") >/dev/null 2>&1; then got=pass; else got=fail; fi + rm -rf "$dir" + if [[ "$got" == "$want" ]]; then + echo "ok $desc" + else + echo "FAIL $desc: expected $want, got $got" + fail=1 + fi +} + +expect pass "pinned action" workflows/a.yml " - uses: actions/checkout@$SHA # v7" +expect pass "pinned reusable workflow" workflows/a.yml " uses: org/repo/.github/workflows/x.yml@$SHA # v1" +expect pass "local action" workflows/a.yml " - uses: ./.github/actions/foo" +expect pass "quoted local action" workflows/a.yml " - uses: './.github/actions/foo'" +expect pass "double-quoted pinned action" workflows/a.yml " - uses: \"actions/checkout@$SHA\" # v7" +expect fail "tag ref" workflows/a.yml " - uses: actions/checkout@v7" +expect fail "double-quoted tag ref" workflows/a.yml " - uses: \"actions/checkout@v7\"" +expect fail "single-quoted tag ref" workflows/a.yml " - uses: 'actions/checkout@v7'" +expect fail "extra sequence spacing" workflows/a.yml " - uses: actions/checkout@v7" +expect fail "unpinned reusable workflow" workflows/a.yml " uses: org/repo/.github/workflows/x.yml@main" +expect fail "unpinned composite action" actions/foo/action.yml " - uses: actions/checkout@v7" +expect fail "short SHA" workflows/a.yml " - uses: actions/checkout@${SHA:0:7}" + +exit "$fail" From 3f09609dd61906432e4b84ae22dc113acdd88055 Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Fri, 25 Sep 2026 14:11:57 +0200 Subject: [PATCH 02/10] fix(osv-scanner): drop merge_group trigger The upstream PR scan checks out $GITHUB_BASE_REF, which is empty for merge_group, so it compared the tree against itself and passed without checking. No repo uses a merge queue. --- .github/workflows/osv-scanner.yml | 12 ++++++++---- README.md | 4 +--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml index 390ac9d..c51b329 100644 --- a/.github/workflows/osv-scanner.yml +++ b/.github/workflows/osv-scanner.yml @@ -1,8 +1,12 @@ name: OSV-Scanner -# Reusable only. The caller's stub carries the triggers (pull_request, -# merge_group, push to main, a weekly schedule); its event decides which job -# runs. See "Governance workflows" in the README. +# Reusable only. The caller's stub carries the triggers (pull_request, push to +# main, a weekly schedule); its event decides which job runs. See "Governance +# workflows" in the README. +# +# No merge_group: the upstream PR scan diffs against $GITHUB_BASE_REF, which is +# empty in a merge queue, so it would pass without comparing anything. No repo +# uses a merge queue; pick a working scan for it when one does. on: workflow_call: @@ -33,7 +37,7 @@ jobs: with: scan-args: ${{ inputs.scan-args }} scan-pr: - if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} + if: ${{ github.event_name == 'pull_request' }} permissions: actions: read security-events: write diff --git a/README.md b/README.md index 68bb856..be6be4f 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ them together with `DEV_KIT_VERSION`. | --- | --- | --- | | `update-action-pins.yml` | `pull_request` | | | `conventional-commits.yml` | `pull_request` (opened, edited, synchronize, reopened) | `.commitlintrc.yml` in the repo | -| `osv-scanner.yml` | `pull_request`, `merge_group`, `push` to main, weekly | optional `scan-args` input | +| `osv-scanner.yml` | `pull_request`, `push` to main, weekly | optional `scan-args` input | | `issues-add-labels.yaml` | `issues` (opened, reopened) | | | `issues-add-to-project.yml` | `issues`, `pull_request` (opened) | `ADD_TO_PROJECT_PAT` secret | | `renovate-auto-approve.yml` | `pull_request` | see the header of the workflow | @@ -290,8 +290,6 @@ name: OSV-Scanner on: pull_request: branches: ["main"] - merge_group: - types: [checks_requested] schedule: - cron: "12 12 * * 1" push: From f13dc9a60ee81888a17089bb41ef5ea761df78d7 Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Fri, 25 Sep 2026 15:35:50 +0200 Subject: [PATCH 03/10] fix(update-action-pins): ignore SHAs in trailing comments The matcher accepted any line containing a 40-hex SHA, so `uses: org/repo@main # @` passed on the SHA in the comment. Strip the comment before matching; covered by a new test case. --- .github/workflows/update-action-pins.yml | 4 +++- scripts/test-update-action-pins.sh | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-action-pins.yml b/.github/workflows/update-action-pins.yml index 9502548..e8d4c92 100644 --- a/.github/workflows/update-action-pins.yml +++ b/.github/workflows/update-action-pins.yml @@ -27,9 +27,11 @@ jobs: # before matching so single- and double-quoted values are both handled, # and allow any YAML sequence spacing: `- uses:` is valid YAML, and a # matcher expecting exactly one space would let an unpinned action - # through unseen. + # through unseen. Drop trailing comments, or `@main # @` would + # pass on the SHA in the comment. unpinned=$(grep -rhE '^[[:space:]]*(-[[:space:]]+)?uses:' .github/ \ | tr -d "\"'" \ + | sed -E 's/[[:space:]]+#.*$//' \ | grep -vE 'uses:[[:space:]]*\./' \ | grep -vE '@[0-9a-f]{40}([[:space:]]|$)' || true) if [[ -n "$unpinned" ]]; then diff --git a/scripts/test-update-action-pins.sh b/scripts/test-update-action-pins.sh index 0bf0850..9e3d89e 100755 --- a/scripts/test-update-action-pins.sh +++ b/scripts/test-update-action-pins.sh @@ -39,6 +39,7 @@ expect fail "single-quoted tag ref" workflows/a.yml " - uses: 'actions/chec expect fail "extra sequence spacing" workflows/a.yml " - uses: actions/checkout@v7" expect fail "unpinned reusable workflow" workflows/a.yml " uses: org/repo/.github/workflows/x.yml@main" expect fail "unpinned composite action" actions/foo/action.yml " - uses: actions/checkout@v7" +expect fail "SHA only in the comment" workflows/a.yml " - uses: actions/checkout@main # @$SHA" expect fail "short SHA" workflows/a.yml " - uses: actions/checkout@${SHA:0:7}" exit "$fail" From 5d07a123db10a585afd22a70c46b4000858a4836 Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Fri, 25 Sep 2026 21:10:29 +0200 Subject: [PATCH 04/10] feat: relock dev-kit on Renovate's dev-kit PR Renovate bumps the dev-kit flake input tag with a regex manager, which can't update flake.lock, so `nix develop` rewrites the lock in CI and diff-check fails. The reusable renovate-dev-kit-lock workflow runs `nix flake update dev-kit` on renovate/dev-kit and commits the lock through the org's dev-kit GitHub App: an API commit is signed by GitHub, and unlike GITHUB_TOKEN it starts CI on the new head. Needs the app and the DEV_KIT_APP_CLIENT_ID / DEV_KIT_APP_PRIVATE_KEY org secrets; not yet run end to end. --- .github/workflows/renovate-dev-kit-lock.yml | 90 +++++++++++++++++++++ README.md | 22 +++++ 2 files changed, 112 insertions(+) create mode 100644 .github/workflows/renovate-dev-kit-lock.yml diff --git a/.github/workflows/renovate-dev-kit-lock.yml b/.github/workflows/renovate-dev-kit-lock.yml new file mode 100644 index 0000000..7b3cc19 --- /dev/null +++ b/.github/workflows/renovate-dev-kit-lock.yml @@ -0,0 +1,90 @@ +name: renovate-dev-kit-lock + +# Reusable only. Commits `nix flake update dev-kit` to Renovate's dev-kit PR. +# Renovate bumps the flake input tag with a regex manager, which can't update +# flake.lock; without this, `nix develop` rewrites the lock in CI and any +# diff-check fails. See "Governance workflows" in the README for the stub. +# +# Commits through the GitHub App's installation token, via the API: GitHub +# signs the commit (required_signatures), and unlike GITHUB_TOKEN it starts CI +# on the new head. renovate-config lists the app's bot in gitIgnoredAuthors, so +# Renovate keeps rebasing the branch; each rebase re-runs this and relocks. + +on: + workflow_call: + inputs: + branch: + description: Renovate's branch for the dev-kit group. + type: string + default: renovate/dev-kit + secrets: + DEV_KIT_APP_CLIENT_ID: + required: true + DEV_KIT_APP_PRIVATE_KEY: + required: true + +permissions: + contents: read + +jobs: + relock: + name: Relock dev-kit + # Only Renovate's own branch in this repo: fork PRs get no secrets, and a + # same-named branch from someone else doesn't get a bot commit. + if: >- + github.event.pull_request.head.ref == inputs.branch && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login == 'renovate[bot]' + runs-on: ubuntu-24.04 + concurrency: + group: renovate-dev-kit-lock-${{ github.event.pull_request.number }} + cancel-in-progress: true + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + - name: Install nix + uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22 + with: + # Upstream Nix and no telemetry, as in setup-nix. + determinate: false + diagnostic-endpoint: '' + - name: Relock dev-kit + id: relock + run: | + nix flake update dev-kit + if git diff --quiet -- flake.lock; then + echo "flake.lock already matches flake.nix" + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + if: steps.relock.outputs.changed == 'true' + id: app + with: + client-id: ${{ secrets.DEV_KIT_APP_CLIENT_ID }} + private-key: ${{ secrets.DEV_KIT_APP_PRIVATE_KEY }} + permission-contents: write + - name: Commit flake.lock + if: steps.relock.outputs.changed == 'true' + env: + GH_TOKEN: ${{ steps.app.outputs.token }} + REPO: ${{ github.repository }} + BRANCH: ${{ github.event.pull_request.head.ref }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + # createCommitOnBranch, not git push: GitHub signs API commits. + # expectedHeadOid makes it fail instead of clobbering a newer push; + # that push re-runs this workflow anyway. + jq -n --arg repo "$REPO" --arg branch "$BRANCH" --arg head "$HEAD_SHA" \ + --arg lock "$(base64 -w0 flake.lock)" '{ + query: "mutation($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid } } }", + variables: { input: { + branch: { repositoryNameWithOwner: $repo, branchName: $branch }, + message: { headline: "chore(deps): relock dev-kit in flake.lock" }, + expectedHeadOid: $head, + fileChanges: { additions: [{ path: "flake.lock", contents: $lock }] } + } } + }' | gh api graphql --input - --jq '.data.createCommitOnBranch.commit.oid' diff --git a/README.md b/README.md index be6be4f..4ad0d95 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,7 @@ them together with `DEV_KIT_VERSION`. | `issues-add-labels.yaml` | `issues` (opened, reopened) | | | `issues-add-to-project.yml` | `issues`, `pull_request` (opened) | `ADD_TO_PROJECT_PAT` secret | | `renovate-auto-approve.yml` | `pull_request` | see the header of the workflow | +| `renovate-dev-kit-lock.yml` | `pull_request` (opened, synchronize, reopened) | `DEV_KIT_APP_*` org secrets | `.github/workflows/update-action-pins.yml`: @@ -346,6 +347,27 @@ jobs: ADD_TO_PROJECT_PAT: ${{ secrets.ADD_TO_PROJECT_PAT }} ``` +`.github/workflows/renovate-dev-kit-lock.yml` commits `nix flake update dev-kit` +to Renovate's `renovate/dev-kit` PR, which bumps the flake input tag but can't +update `flake.lock`. It commits as the org's dev-kit GitHub App, so the commit +is signed and starts CI; any other PR skips it: + +```yaml +name: Relock dev-kit +on: + pull_request: + branches: ["main"] + types: [opened, synchronize, reopened] +permissions: + contents: read +jobs: + relock: + uses: opendefensecloud/dev-kit/.github/workflows/renovate-dev-kit-lock.yml@<40-char-sha> # + secrets: + DEV_KIT_APP_CLIENT_ID: ${{ secrets.DEV_KIT_APP_CLIENT_ID }} + DEV_KIT_APP_PRIVATE_KEY: ${{ secrets.DEV_KIT_APP_PRIVATE_KEY }} +``` + A job from a reusable workflow reports as ` / `, so `REPO_STATUS_CHECKS` needs the new names after switching to a stub, e.g. `update-action-pins / Check action pins`, `conventional-commits / PR Title` and From d3c94a1d30f522d3d7fcf998dc3020683e15bb8a Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Mon, 28 Sep 2026 09:29:41 +0200 Subject: [PATCH 05/10] docs: note that the stubs don't support merge queues No stub triggers on merge_group. A repo that enables a merge queue has to add the trigger to every stub whose check it requires, and must not require the osv-scanner PR scan, which diffs against an empty GITHUB_BASE_REF there. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 4ad0d95..e2c981f 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,12 @@ them together with `DEV_KIT_VERSION`. | `renovate-auto-approve.yml` | `pull_request` | see the header of the workflow | | `renovate-dev-kit-lock.yml` | `pull_request` (opened, synchronize, reopened) | `DEV_KIT_APP_*` org secrets | +No stub triggers on `merge_group`, as no repo uses a merge queue. A repo that +turns one on must add the trigger to every stub whose check it requires, and +must not require `osv-scanner / scan-pr / osv-scan`: that PR scan diffs +against `$GITHUB_BASE_REF`, which is empty in a merge queue (see +`osv-scanner.yml`). + `.github/workflows/update-action-pins.yml`: ```yaml From 7f25b98f8de4a38fdb0beec2abe62b75740929b8 Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Tue, 29 Sep 2026 15:18:26 +0200 Subject: [PATCH 06/10] refactor(renovate-dev-kit-lock): relock with nix flake lock nix flake lock relocks only inputs whose flake.nix reference changed, so the workflow doesn't have to name the dev-kit input and matches what renovate-config#19 expects. Same result on Solar: only the dev-kit node changes, and a second run is a no-op. --- .github/workflows/renovate-dev-kit-lock.yml | 6 ++++-- README.md | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/renovate-dev-kit-lock.yml b/.github/workflows/renovate-dev-kit-lock.yml index 7b3cc19..2a68fbb 100644 --- a/.github/workflows/renovate-dev-kit-lock.yml +++ b/.github/workflows/renovate-dev-kit-lock.yml @@ -1,6 +1,8 @@ name: renovate-dev-kit-lock -# Reusable only. Commits `nix flake update dev-kit` to Renovate's dev-kit PR. +# Reusable only. Commits `nix flake lock` to Renovate's dev-kit PR. It relocks +# only inputs whose flake.nix reference changed, here the bumped dev-kit tag, +# and is a no-op otherwise, so a re-run after its own commit changes nothing. # Renovate bumps the flake input tag with a regex manager, which can't update # flake.lock; without this, `nix develop` rewrites the lock in CI and any # diff-check fails. See "Governance workflows" in the README for the stub. @@ -53,7 +55,7 @@ jobs: - name: Relock dev-kit id: relock run: | - nix flake update dev-kit + nix flake lock if git diff --quiet -- flake.lock; then echo "flake.lock already matches flake.nix" echo "changed=false" >> "$GITHUB_OUTPUT" diff --git a/README.md b/README.md index e2c981f..75c8749 100644 --- a/README.md +++ b/README.md @@ -353,8 +353,8 @@ jobs: ADD_TO_PROJECT_PAT: ${{ secrets.ADD_TO_PROJECT_PAT }} ``` -`.github/workflows/renovate-dev-kit-lock.yml` commits `nix flake update dev-kit` -to Renovate's `renovate/dev-kit` PR, which bumps the flake input tag but can't +`.github/workflows/renovate-dev-kit-lock.yml` commits `nix flake lock` to +Renovate's `renovate/dev-kit` PR, which bumps the flake input tag but can't update `flake.lock`. It commits as the org's dev-kit GitHub App, so the commit is signed and starts CI; any other PR skips it: From 839cd39f672b342317033306b1a243efda115e81 Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Fri, 2 Oct 2026 12:02:15 +0200 Subject: [PATCH 07/10] fix(renovate-dev-kit-lock): use the org's DEV_KIT_BOT_APP_* secret names --- .github/workflows/renovate-dev-kit-lock.yml | 8 ++++---- README.md | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/renovate-dev-kit-lock.yml b/.github/workflows/renovate-dev-kit-lock.yml index 2a68fbb..3270c06 100644 --- a/.github/workflows/renovate-dev-kit-lock.yml +++ b/.github/workflows/renovate-dev-kit-lock.yml @@ -20,9 +20,9 @@ on: type: string default: renovate/dev-kit secrets: - DEV_KIT_APP_CLIENT_ID: + DEV_KIT_BOT_APP_CLIENT_ID: required: true - DEV_KIT_APP_PRIVATE_KEY: + DEV_KIT_BOT_APP_PRIVATE_KEY: required: true permissions: @@ -66,8 +66,8 @@ jobs: if: steps.relock.outputs.changed == 'true' id: app with: - client-id: ${{ secrets.DEV_KIT_APP_CLIENT_ID }} - private-key: ${{ secrets.DEV_KIT_APP_PRIVATE_KEY }} + client-id: ${{ secrets.DEV_KIT_BOT_APP_CLIENT_ID }} + private-key: ${{ secrets.DEV_KIT_BOT_APP_PRIVATE_KEY }} permission-contents: write - name: Commit flake.lock if: steps.relock.outputs.changed == 'true' diff --git a/README.md b/README.md index 75c8749..b8806e8 100644 --- a/README.md +++ b/README.md @@ -254,7 +254,7 @@ them together with `DEV_KIT_VERSION`. | `issues-add-labels.yaml` | `issues` (opened, reopened) | | | `issues-add-to-project.yml` | `issues`, `pull_request` (opened) | `ADD_TO_PROJECT_PAT` secret | | `renovate-auto-approve.yml` | `pull_request` | see the header of the workflow | -| `renovate-dev-kit-lock.yml` | `pull_request` (opened, synchronize, reopened) | `DEV_KIT_APP_*` org secrets | +| `renovate-dev-kit-lock.yml` | `pull_request` (opened, synchronize, reopened) | `DEV_KIT_BOT_APP_*` org secrets | No stub triggers on `merge_group`, as no repo uses a merge queue. A repo that turns one on must add the trigger to every stub whose check it requires, and @@ -370,8 +370,8 @@ jobs: relock: uses: opendefensecloud/dev-kit/.github/workflows/renovate-dev-kit-lock.yml@<40-char-sha> # secrets: - DEV_KIT_APP_CLIENT_ID: ${{ secrets.DEV_KIT_APP_CLIENT_ID }} - DEV_KIT_APP_PRIVATE_KEY: ${{ secrets.DEV_KIT_APP_PRIVATE_KEY }} + DEV_KIT_BOT_APP_CLIENT_ID: ${{ secrets.DEV_KIT_BOT_APP_CLIENT_ID }} + DEV_KIT_BOT_APP_PRIVATE_KEY: ${{ secrets.DEV_KIT_BOT_APP_PRIVATE_KEY }} ``` A job from a reusable workflow reports as ` / `, so From 57cf74ca598628fec0c4043815a9902aeb95376a Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Mon, 5 Oct 2026 12:09:59 +0200 Subject: [PATCH 08/10] chore: apply dev-kit formatting after rebase onto #34 --- README.md | 24 ++++++++++++------------ scripts/test-update-action-pins.sh | 7 +++++-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b8806e8..ad6884c 100644 --- a/README.md +++ b/README.md @@ -187,18 +187,18 @@ It configures: The repository settings are configurable via make variables (set them in your `Makefile` or pass them on the command line, e.g. `make repo-settings REPO_STATUS_CHECKS='["CI","lint"]' REPO_RULESET_BRANCHES='["release/*"]'`): -| Variable | Default | Description | -| -------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `REPO_ALLOW_MERGE_COMMIT` | `true` | Allow merge commits in the merge strategy | -| `REPO_ALLOW_SQUASH_MERGE` | `false` | Allow squash merging | -| `REPO_ALLOW_REBASE_MERGE` | `false` | Allow rebase merging | -| `REPO_REQUIRE_LAST_PUSH_APPROVAL` | `false` | Require the most recent push to be approved before merging | -| `REPO_ADMIN_BYPASS` | `true` | When `false`, org admins cannot bypass the ruleset | -| `REPO_REQUIRED_APPROVING_REVIEW_COUNT` | `1` | Number of approving reviews required to merge | -| `REPO_REQUIRE_CODE_OWNER_REVIEW` | `false` | Require an approving review from code owners | -| `REPO_REQUIRE_BRANCH_UP_TO_DATE` | `false` | Require branches to be up to date before merging (needs at least one `REPO_STATUS_CHECKS` value; `repo-settings` fails otherwise) | +| Variable | Default | Description | +| -------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `REPO_ALLOW_MERGE_COMMIT` | `true` | Allow merge commits in the merge strategy | +| `REPO_ALLOW_SQUASH_MERGE` | `false` | Allow squash merging | +| `REPO_ALLOW_REBASE_MERGE` | `false` | Allow rebase merging | +| `REPO_REQUIRE_LAST_PUSH_APPROVAL` | `false` | Require the most recent push to be approved before merging | +| `REPO_ADMIN_BYPASS` | `true` | When `false`, org admins cannot bypass the ruleset | +| `REPO_REQUIRED_APPROVING_REVIEW_COUNT` | `1` | Number of approving reviews required to merge | +| `REPO_REQUIRE_CODE_OWNER_REVIEW` | `false` | Require an approving review from code owners | +| `REPO_REQUIRE_BRANCH_UP_TO_DATE` | `false` | Require branches to be up to date before merging (needs at least one `REPO_STATUS_CHECKS` value; `repo-settings` fails otherwise) | | `REPO_STATUS_CHECKS` | `[]` | JSON array of status-check contexts that must pass (e.g. `["CI","update-action-pins / Check action pins"]`); each job name is used as-is, so contexts with spaces work; the `required_status_checks` rule is only added when this is non-empty | -| `REPO_RULESET_BRANCHES` | `[]` | JSON array of additional branch patterns the ruleset applies to (e.g. `["release/*"]`); short names are normalized to `refs/heads/...`; the default branch is always protected | +| `REPO_RULESET_BRANCHES` | `[]` | JSON array of additional branch patterns the ruleset applies to (e.g. `["release/*"]`); short names are normalized to `refs/heads/...`; the default branch is always protected | ### GitHub Actions @@ -247,7 +247,7 @@ and calls dev-kit. Pin each stub to a SHA like any other action; Renovate bumps them together with `DEV_KIT_VERSION`. | Workflow | Stub triggers | Needs | -| --- | --- | --- | +| --------------------------- | ------------------------------------------------------ | ------------------------------- | | `update-action-pins.yml` | `pull_request` | | | `conventional-commits.yml` | `pull_request` (opened, edited, synchronize, reopened) | `.commitlintrc.yml` in the repo | | `osv-scanner.yml` | `pull_request`, `push` to main, weekly | optional `scan-args` input | diff --git a/scripts/test-update-action-pins.sh b/scripts/test-update-action-pins.sh index 9e3d89e..7fd8fa4 100755 --- a/scripts/test-update-action-pins.sh +++ b/scripts/test-update-action-pins.sh @@ -7,7 +7,10 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" CHECK="$(yq '.jobs.check-pins.steps[] | select(.name == "Verify all actions are pinned to a SHA") | .run' \ "$ROOT/.github/workflows/update-action-pins.yml")" -[[ -n "$CHECK" ]] || { echo "FAIL: pin check step not found"; exit 1; } +[[ -n "$CHECK" ]] || { + echo "FAIL: pin check step not found" + exit 1 +} SHA=0123456789abcdef0123456789abcdef01234567 fail=0 @@ -18,7 +21,7 @@ expect() { dir="$(mktemp -d)" mkdir -p "$dir/.github/$(dirname "$file")" printf '%s\n' "$content" > "$dir/.github/$file" - if (cd "$dir" && bash -c "$CHECK") >/dev/null 2>&1; then got=pass; else got=fail; fi + if (cd "$dir" && bash -c "$CHECK") > /dev/null 2>&1; then got=pass; else got=fail; fi rm -rf "$dir" if [[ "$got" == "$want" ]]; then echo "ok $desc" From edf648f4032e35fec96024a6dc183f6af56e7e5d Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Tue, 6 Oct 2026 09:01:16 +0200 Subject: [PATCH 09/10] feat(renovate-dev-kit-lock): take the Renovate login as an input Same renovate-actor input as renovate-auto-approve, defaulting to renovate[bot], so a self-hosted Renovate under another login works. --- .github/workflows/renovate-dev-kit-lock.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/renovate-dev-kit-lock.yml b/.github/workflows/renovate-dev-kit-lock.yml index 3270c06..1715f96 100644 --- a/.github/workflows/renovate-dev-kit-lock.yml +++ b/.github/workflows/renovate-dev-kit-lock.yml @@ -19,6 +19,10 @@ on: description: Renovate's branch for the dev-kit group. type: string default: renovate/dev-kit + renovate-actor: + description: Login of the Renovate app that authors the pull requests. + type: string + default: renovate[bot] secrets: DEV_KIT_BOT_APP_CLIENT_ID: required: true @@ -36,7 +40,7 @@ jobs: if: >- github.event.pull_request.head.ref == inputs.branch && github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.user.login == 'renovate[bot]' + github.event.pull_request.user.login == inputs.renovate-actor runs-on: ubuntu-24.04 concurrency: group: renovate-dev-kit-lock-${{ github.event.pull_request.number }} From 5954b173731db9a588c0ee4e6d202432354ac7b6 Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Tue, 6 Oct 2026 11:56:03 +0200 Subject: [PATCH 10/10] ci: pin runs-on to ubuntu-24.04 Replace ubuntu-latest in every dev-kit workflow, and pass the same runner to the upstream OSV jobs, so the reusable workflows consumers call don't change OS under them. Renovate proposes runner bumps as updates. --- .github/workflows/conventional-commits.yml | 4 ++-- .github/workflows/issues-add-labels.yaml | 2 +- .github/workflows/issues-add-to-project.yml | 2 +- .github/workflows/osv-scanner.yml | 2 ++ .github/workflows/release-drafter.yaml | 2 +- .github/workflows/renovate-auto-approve.yml | 2 +- .github/workflows/update-action-pins.yml | 2 +- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml index c2a7a72..b3a45fd 100644 --- a/.github/workflows/conventional-commits.yml +++ b/.github/workflows/conventional-commits.yml @@ -12,7 +12,7 @@ permissions: jobs: pr-title: name: PR Title - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6 env: @@ -32,7 +32,7 @@ jobs: commitlint: name: Commit Messages - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: diff --git a/.github/workflows/issues-add-labels.yaml b/.github/workflows/issues-add-labels.yaml index 87695d0..11703b0 100644 --- a/.github/workflows/issues-add-labels.yaml +++ b/.github/workflows/issues-add-labels.yaml @@ -10,7 +10,7 @@ on: workflow_call: jobs: label_issues: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: issues: write steps: diff --git a/.github/workflows/issues-add-to-project.yml b/.github/workflows/issues-add-to-project.yml index 5417967..4feb0e8 100644 --- a/.github/workflows/issues-add-to-project.yml +++ b/.github/workflows/issues-add-to-project.yml @@ -18,7 +18,7 @@ on: jobs: add-to-project: name: Add issue to project - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 # Authenticates with ADD_TO_PROJECT_PAT, so the workflow GITHUB_TOKEN needs no scopes. permissions: {} steps: diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml index c51b329..cc5f552 100644 --- a/.github/workflows/osv-scanner.yml +++ b/.github/workflows/osv-scanner.yml @@ -36,6 +36,7 @@ jobs: uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@a345acffa64b0eaede81a3d9aae6141214d9c8fc # v2.6.0 with: scan-args: ${{ inputs.scan-args }} + runs-on: ubuntu-24.04 scan-pr: if: ${{ github.event_name == 'pull_request' }} permissions: @@ -45,3 +46,4 @@ jobs: uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@a345acffa64b0eaede81a3d9aae6141214d9c8fc # v2.6.0 with: scan-args: ${{ inputs.scan-args }} + runs-on: ubuntu-24.04 diff --git a/.github/workflows/release-drafter.yaml b/.github/workflows/release-drafter.yaml index dd7567d..27adb89 100644 --- a/.github/workflows/release-drafter.yaml +++ b/.github/workflows/release-drafter.yaml @@ -25,7 +25,7 @@ jobs: # write permission is required for autolabeler # otherwise, read permission is required at least pull-requests: write - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: # (Optional) GitHub Enterprise requires GHE_HOST variable set #- name: Set GHE_HOST diff --git a/.github/workflows/renovate-auto-approve.yml b/.github/workflows/renovate-auto-approve.yml index 7078d44..f449cfc 100644 --- a/.github/workflows/renovate-auto-approve.yml +++ b/.github/workflows/renovate-auto-approve.yml @@ -47,7 +47,7 @@ concurrency: jobs: auto-approve: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: >- github.event_name == 'pull_request' && github.event.pull_request.user.login == inputs.renovate-actor && diff --git a/.github/workflows/update-action-pins.yml b/.github/workflows/update-action-pins.yml index e8d4c92..f999b70 100644 --- a/.github/workflows/update-action-pins.yml +++ b/.github/workflows/update-action-pins.yml @@ -16,7 +16,7 @@ on: jobs: check-pins: name: Check action pins - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: