From a5cd1f5c611d35897a50c6bef212a1bd5d341088 Mon Sep 17 00:00:00 2001 From: jupblb Date: Thu, 14 May 2026 07:24:46 +0200 Subject: [PATCH 1/2] ci: combine fix + nix-checks into one sequenced workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renovate dep-bump PRs need post-processing (per-module `go mod tidy` and `nix-update` of vendor hashes) before the Nix checks can build them. Today this happens in two parallel workflows: the doomed nix-checks run starts at the same time as the fix and shows red until the fix re-triggers it. Combine them into a single ci workflow with proper ordering: fix --> list --> {checks, packages} --> ci-pass The fix job uses dorny/paths-filter to cheaply detect whether the PR touches any of the files we know how to fix; if not, the rest of the job no-ops and downstream jobs run normally. If the fix pushes corrective commits, downstream jobs are skipped — the resulting `pull_request synchronize` triggers a fresh CI run on the corrected SHA that does the actual validation. A new `ci-pass` aggregator (always() + needs all four) is the single required status check for branch protection: it passes when either the fix pushed (next run validates) or every downstream job succeeded. Skipped fix (forks) or empty-fix paths fall through to the downstream-result check, so non-Renovate PRs behave exactly as before. Supersedes the standalone update-vendor-hash / fix-renovate-prs workflow and the standalone nix-checks workflow; both files are removed. --- .github/workflows/ci.yaml | 205 ++++++++++++++++++++++ .github/workflows/nix-checks.yaml | 66 ------- .github/workflows/update-vendor-hash.yaml | 89 ---------- 3 files changed, 205 insertions(+), 155 deletions(-) create mode 100644 .github/workflows/ci.yaml delete mode 100644 .github/workflows/nix-checks.yaml delete mode 100644 .github/workflows/update-vendor-hash.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..f7275a62 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,205 @@ +name: ci + +# Combined CI pipeline: a `fix` job runs first, post-processing Renovate +# (or any same-repo) PRs that bump Go modules / npm manifests so the +# workspace ends up in a state the Nix build can consume — i.e. tidies +# every Go module to add missing go.sum entries +# (https://github.com/golang/go/issues/63901) and recomputes the vendor +# hashes in flake.nix / checks.nix with `nix-update`. Downstream +# nix-checks jobs (`list`, `checks`, `packages`) only run after `fix` +# completes, and are skipped if `fix` pushed corrective commits — that +# push fires `pull_request synchronize` and a fresh CI run on the +# corrected SHA does the actual validation. + +on: + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + fix: + # Forks can't be pushed to (their GITHUB_TOKEN is read-only and + # secrets are not exposed); skip the job entirely there. + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + outputs: + pushed: ${{ steps.push.outputs.pushed }} + steps: + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + relevant: + - 'go.mod' + - 'go.sum' + - 'bindings/go/scip/go.mod' + - 'bindings/go/scip/go.sum' + - 'reprolang/go.mod' + - 'reprolang/go.sum' + - 'bindings/typescript/package.json' + - 'bindings/typescript/package-lock.json' + + - name: Generate GitHub App token + if: steps.filter.outputs.relevant == 'true' + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ vars.RENOVATE_FIX_APP_ID }} + private-key: ${{ secrets.RENOVATE_FIX_APP_PRIVATE_KEY }} + + - if: steps.filter.outputs.relevant == 'true' + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.ref }} + # Pushing under the App identity (not GITHUB_TOKEN) makes + # `git push` fire `pull_request synchronize`, which triggers + # a fresh CI run that revalidates the corrected SHA. + token: ${{ steps.app-token.outputs.token }} + + - if: steps.filter.outputs.relevant == 'true' + uses: DeterminateSystems/nix-installer-action@v22 + with: + summarize: false + - if: steps.filter.outputs.relevant == 'true' + uses: DeterminateSystems/magic-nix-cache-action@v13 + + - name: Tidy Go modules + if: steps.filter.outputs.relevant == 'true' + env: + GOWORK: 'off' + # https://github.com/golang/go/issues/63901 + run: | + set -euo pipefail + for dir in bindings/go/scip . reprolang; do + (cd "$dir" && nix develop --command go mod tidy) + done + + - name: Recompute vendor hashes with nix-update + if: steps.filter.outputs.relevant == 'true' + run: | + set -euo pipefail + # nix-update only auto-resolves packages..; for + # attributes under `checks` we must pass the full dotted path. + # One attribute per invocation; sequential to avoid concurrent + # writes to the same .nix files. + for attr in \ + packages.x86_64-linux.scip \ + checks.x86_64-linux.go-bindings \ + checks.x86_64-linux.reprolang \ + checks.x86_64-linux.typescript-bindings; do + nix run github:Mic92/nix-update -- \ + --flake --version=skip "$attr" + done + + - name: Commit and push + id: push + if: steps.filter.outputs.relevant == 'true' + run: | + set -euo pipefail + if git diff --quiet; then + echo "No changes; nothing to push." + echo "pushed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -am 'chore: tidy Go modules and update vendor hashes' + git push + echo "pushed=true" >> "$GITHUB_OUTPUT" + + list: + needs: fix + # Run when `fix` succeeded with no push, was skipped (fork), or + # didn't need to do anything (filter said not relevant). Skip when + # `fix` pushed corrective commits — the resulting `pull_request + # synchronize` triggers a fresh CI run that does the validation. + if: >- + always() && + needs.fix.outputs.pushed != 'true' && + needs.fix.result != 'failure' && + needs.fix.result != 'cancelled' + runs-on: ubuntu-latest + outputs: + checks: ${{ steps.checks.outputs.result }} + packages: ${{ steps.packages.outputs.result }} + steps: + - uses: actions/checkout@v6 + - uses: DeterminateSystems/nix-installer-action@v22 + - id: checks + run: | + CHECKS=$(nix eval .#checks.x86_64-linux \ + --apply builtins.attrNames --json) + echo "result=$CHECKS" >> "$GITHUB_OUTPUT" + - id: packages + run: | + PACKAGES=$(nix eval .#packages.x86_64-linux \ + --apply 'ps: builtins.attrNames (removeAttrs ps ["default"])' \ + --json) + echo "result=$PACKAGES" >> "$GITHUB_OUTPUT" + + checks: + needs: list + if: needs.list.result == 'success' + runs-on: ubuntu-latest + strategy: + matrix: + check: ${{ fromJSON(needs.list.outputs.checks) }} + steps: + - uses: actions/checkout@v6 + - uses: DeterminateSystems/nix-installer-action@v22 + - uses: DeterminateSystems/magic-nix-cache-action@v13 + - run: nix build .#checks.x86_64-linux.${{ matrix.check }} + + packages: + needs: list + if: needs.list.result == 'success' + runs-on: ubuntu-latest + strategy: + matrix: + package: ${{ fromJSON(needs.list.outputs.packages) }} + steps: + - uses: actions/checkout@v6 + - uses: DeterminateSystems/nix-installer-action@v22 + - uses: DeterminateSystems/magic-nix-cache-action@v13 + - run: nix run .#${{ matrix.package }} + - run: git diff --exit-code + + # Single required check for branch protection. Passes if either: + # * `fix` pushed corrective commits (the next CI run will validate), or + # * `fix` did not push and every downstream nix-checks job succeeded. + ci-pass: + if: always() + needs: [fix, list, checks, packages] + runs-on: ubuntu-latest + steps: + - env: + FIX_RESULT: ${{ needs.fix.result }} + FIX_PUSHED: ${{ needs.fix.outputs.pushed }} + LIST_RESULT: ${{ needs.list.result }} + CHECKS_RESULT: ${{ needs.checks.result }} + PACKAGES_RESULT: ${{ needs.packages.result }} + run: | + set -euo pipefail + case "$FIX_RESULT" in + failure|cancelled) + echo "fix job $FIX_RESULT" + exit 1 + ;; + esac + if [[ "$FIX_PUSHED" == "true" ]]; then + echo "fix pushed; downstream skipped (next run will validate)" + exit 0 + fi + for r in "$LIST_RESULT" "$CHECKS_RESULT" "$PACKAGES_RESULT"; do + case "$r" in + failure|cancelled) + echo "downstream job $r" + exit 1 + ;; + esac + done diff --git a/.github/workflows/nix-checks.yaml b/.github/workflows/nix-checks.yaml deleted file mode 100644 index acde58be..00000000 --- a/.github/workflows/nix-checks.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: Nix - -on: - pull_request: - -# Cancel an in-progress run when a newer commit supersedes it on the same -# branch (e.g. when update-vendor-hash pushes a hash fix). -concurrency: - group: nix-checks-${{ github.head_ref || github.ref }} - cancel-in-progress: true - -jobs: - list: - runs-on: ubuntu-latest - outputs: - checks: ${{ steps.checks.outputs.result }} - packages: ${{ steps.packages.outputs.result }} - steps: - - uses: actions/checkout@v6 - - uses: DeterminateSystems/nix-installer-action@v22 - - id: checks - run: | - CHECKS=$(nix eval .#checks.x86_64-linux \ - --apply builtins.attrNames --json) - echo "result=$CHECKS" >> "$GITHUB_OUTPUT" - - id: packages - run: | - PACKAGES=$(nix eval .#packages.x86_64-linux \ - --apply 'ps: builtins.attrNames (removeAttrs ps ["default"])' \ - --json) - echo "result=$PACKAGES" >> "$GITHUB_OUTPUT" - - checks: - needs: list - runs-on: ubuntu-latest - strategy: - matrix: - check: ${{ fromJSON(needs.list.outputs.checks) }} - steps: - - uses: actions/checkout@v6 - - uses: DeterminateSystems/nix-installer-action@v22 - - uses: DeterminateSystems/magic-nix-cache-action@v13 - - run: nix build .#checks.x86_64-linux.${{ matrix.check }} - - packages: - needs: list - runs-on: ubuntu-latest - strategy: - matrix: - package: ${{ fromJSON(needs.list.outputs.packages) }} - steps: - - uses: actions/checkout@v6 - - uses: DeterminateSystems/nix-installer-action@v22 - - uses: DeterminateSystems/magic-nix-cache-action@v13 - - run: nix run .#${{ matrix.package }} - - run: git diff --exit-code - - nix-checks-pass: - if: always() - needs: [list, checks, packages] - runs-on: ubuntu-latest - steps: - - if: >- - contains(needs.*.result, 'failure') || - contains(needs.*.result, 'cancelled') - run: exit 1 diff --git a/.github/workflows/update-vendor-hash.yaml b/.github/workflows/update-vendor-hash.yaml deleted file mode 100644 index 57954447..00000000 --- a/.github/workflows/update-vendor-hash.yaml +++ /dev/null @@ -1,89 +0,0 @@ -name: update-vendor-hash - -# Renovate updates go.mod / go.sum (and TypeScript package manifests) but -# cannot update the vendor hashes in flake.nix / checks.nix, which causes -# the Nix build to fail until the hashes are fixed by hand. This workflow -# watches PRs that modify those files, recomputes the affected hashes with -# `nix-update`, and pushes the corrected files back to the PR branch using -# a GitHub App token. Pushing under a non-GITHUB_TOKEN identity makes the -# push fire `pull_request synchronize` naturally, producing a check_suite -# the PR UI displays. - -on: - pull_request: - paths: - - go.mod - - go.sum - - bindings/go/scip/go.mod - - bindings/go/scip/go.sum - - reprolang/go.mod - - reprolang/go.sum - - bindings/typescript/package.json - - bindings/typescript/package-lock.json - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - update: - if: github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - steps: - - name: Generate GitHub App token - id: app-token - uses: actions/create-github-app-token@v3 - with: - app-id: ${{ vars.RENOVATE_FIX_APP_ID }} - private-key: ${{ secrets.RENOVATE_FIX_APP_PRIVATE_KEY }} - - - uses: actions/checkout@v6 - with: - ref: ${{ github.event.pull_request.head.ref }} - token: ${{ steps.app-token.outputs.token }} - - - uses: DeterminateSystems/nix-installer-action@v22 - with: - summarize: false - - uses: DeterminateSystems/magic-nix-cache-action@v13 - - - name: Tidy Go modules - env: - GOWORK: 'off' - run: | - set -euo pipefail - # https://github.com/golang/go/issues/63901 - for dir in bindings/go/scip . reprolang; do - (cd "$dir" && nix develop --command go mod tidy) - done - - - name: Recompute vendor hashes with nix-update - run: | - set -euo pipefail - # nix-update only auto-resolves packages..; for - # attributes under `checks` we must pass the full dotted path. - # One attribute per invocation; sequential to avoid concurrent - # writes to the same .nix files. - for attr in \ - packages.x86_64-linux.scip \ - checks.x86_64-linux.go-bindings \ - checks.x86_64-linux.reprolang \ - checks.x86_64-linux.typescript-bindings; do - nix run github:Mic92/nix-update -- \ - --flake --version=skip "$attr" - done - - - name: Commit and push - run: | - set -euo pipefail - if git diff --quiet; then - echo "No changes; nothing to push." - exit 0 - fi - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -am 'chore: tidy Go modules and update vendor hashes' - git push From 66708109aeb31cbcd97abdc1b291b35fb4249922 Mon Sep 17 00:00:00 2001 From: jupblb Date: Thu, 14 May 2026 07:39:06 +0200 Subject: [PATCH 2/2] ci: simplify list gating and ci-pass - list now only checks needs.fix.outputs.pushed != true; if fix failed or was cancelled, list is skipped (default needs behavior) and ci-pass fails on the failure/cancelled result. - drop redundant if: needs.list.result == success on checks/packages; needs already short-circuits when list is skipped/failed. - collapse ci-pass to a contains(needs.*.result, ...) one-liner; the previous shell logic for distinguishing fix-pushed vs all-passed is unnecessary because a fix push retriggers CI on the new SHA and branch protection on the PR HEAD blocks merge until that succeeds. --- .github/workflows/ci.yaml | 67 ++++++++++----------------------------- 1 file changed, 17 insertions(+), 50 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f7275a62..b0883ecd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,15 +1,13 @@ name: ci -# Combined CI pipeline: a `fix` job runs first, post-processing Renovate -# (or any same-repo) PRs that bump Go modules / npm manifests so the -# workspace ends up in a state the Nix build can consume — i.e. tidies -# every Go module to add missing go.sum entries -# (https://github.com/golang/go/issues/63901) and recomputes the vendor -# hashes in flake.nix / checks.nix with `nix-update`. Downstream -# nix-checks jobs (`list`, `checks`, `packages`) only run after `fix` -# completes, and are skipped if `fix` pushed corrective commits — that -# push fires `pull_request synchronize` and a fresh CI run on the -# corrected SHA does the actual validation. +# Combined CI pipeline. `fix` runs first, post-processing same-repo PRs +# that bump Go modules / npm manifests so the workspace ends up in a +# state the Nix build can consume (per-module `go mod tidy` plus +# `nix-update` of vendor hashes; see https://github.com/golang/go/issues/63901). +# If `fix` pushed a corrective commit, downstream nix-checks jobs are +# skipped — the resulting `pull_request synchronize` triggers a fresh +# CI run on the corrected SHA, and branch protection on the PR HEAD +# prevents merging until that fresh run reports success. on: pull_request: @@ -114,15 +112,11 @@ jobs: list: needs: fix - # Run when `fix` succeeded with no push, was skipped (fork), or - # didn't need to do anything (filter said not relevant). Skip when - # `fix` pushed corrective commits — the resulting `pull_request - # synchronize` triggers a fresh CI run that does the validation. - if: >- - always() && - needs.fix.outputs.pushed != 'true' && - needs.fix.result != 'failure' && - needs.fix.result != 'cancelled' + # Skip downstream when `fix` pushed corrective commits — the + # resulting `pull_request synchronize` triggers a fresh CI run that + # validates the corrected SHA, and branch protection on the PR + # HEAD prevents merging until that fresh run reports success. + if: always() && needs.fix.outputs.pushed != 'true' runs-on: ubuntu-latest outputs: checks: ${{ steps.checks.outputs.result }} @@ -144,7 +138,6 @@ jobs: checks: needs: list - if: needs.list.result == 'success' runs-on: ubuntu-latest strategy: matrix: @@ -157,7 +150,6 @@ jobs: packages: needs: list - if: needs.list.result == 'success' runs-on: ubuntu-latest strategy: matrix: @@ -169,37 +161,12 @@ jobs: - run: nix run .#${{ matrix.package }} - run: git diff --exit-code - # Single required check for branch protection. Passes if either: - # * `fix` pushed corrective commits (the next CI run will validate), or - # * `fix` did not push and every downstream nix-checks job succeeded. ci-pass: if: always() needs: [fix, list, checks, packages] runs-on: ubuntu-latest steps: - - env: - FIX_RESULT: ${{ needs.fix.result }} - FIX_PUSHED: ${{ needs.fix.outputs.pushed }} - LIST_RESULT: ${{ needs.list.result }} - CHECKS_RESULT: ${{ needs.checks.result }} - PACKAGES_RESULT: ${{ needs.packages.result }} - run: | - set -euo pipefail - case "$FIX_RESULT" in - failure|cancelled) - echo "fix job $FIX_RESULT" - exit 1 - ;; - esac - if [[ "$FIX_PUSHED" == "true" ]]; then - echo "fix pushed; downstream skipped (next run will validate)" - exit 0 - fi - for r in "$LIST_RESULT" "$CHECKS_RESULT" "$PACKAGES_RESULT"; do - case "$r" in - failure|cancelled) - echo "downstream job $r" - exit 1 - ;; - esac - done + - if: >- + contains(needs.*.result, 'failure') || + contains(needs.*.result, 'cancelled') + run: exit 1