From 02082a06ff9d199469762a4a6c8231709f262502 Mon Sep 17 00:00:00 2001 From: Griffin Long <151978260+DrunkOnJava@users.noreply.github.com> Date: Thu, 5 Nov 2026 18:35:39 -0500 Subject: [PATCH 1/3] ci(codeql): a failed SARIF upload is retried once, in a job of its own (#1499) analyze uploaded its own SARIF and Analyze (rust) failed on #1468 with the log ending at Uploading results and no finding, the failure class #1461 gave Scorecard and zizmor a retry for. analyze now writes the SARIF without uploading it (upload: never), a step of its own uploads it and may fail without failing the job, and a failure leaves the SARIF as an artifact; analyze-retry, a job of its own because codeql-action refuses a second upload of one category in a job, uploads what the artifacts hold with the same category, analysis key and matrix. A real rejection fails the retry and the run. Closes #1499. --- .github/workflows/codeql.yml | 127 +++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index cf9e27f9d..d4643f29b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -15,6 +15,18 @@ # CodeQL analyses here) starts no run, so it has no CodeQL check to wait for # and no head analysis; `main` is still analysed on every push, so its # baseline stays current. +# +# A failed upload of the results is tried once more (#1499). `analyze` used to +# upload its own SARIF, and the upload failed once with no finding, the log +# ending at "Uploading results" (run 42128776536, Analyze (rust), on #1468), as +# Scorecard's and zizmor's did; arm.sh refuses a head with a red check, so the +# landing waited for a hand rerun. Now `analyze` writes the SARIF and does not +# upload it, a step of its own uploads it and may fail without failing the job, +# and the failure leaves the SARIF as an artifact. The retry is the job +# `analyze-retry`: codeql-action refuses a second upload of one category in a +# job (upload-lib.ts exports a sentinel into the job's GITHUB_ENV before it +# sends anything), so a second step in the same job would fail at once whatever +# the first died of. A real rejection of the file fails the retry, and the run. name: CodeQL @@ -65,6 +77,121 @@ jobs: with: languages: ${{ matrix.language }} build-mode: none + # `upload: never` leaves the SARIF in `output`, with the category already + # in it (analyze passes it to the CLI), and uploads nothing. The overlay + # base database on `main` is still saved by this step. - uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: category: "/language:${{ matrix.language }}" + upload: never + output: ${{ runner.temp }}/codeql-sarif + # The upload analyze did itself, with the same category. It waits for + # processing as analyze did, so a file code scanning rejects fails here. + - name: Upload the results to code scanning + id: sarif + continue-on-error: true + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 + with: + sarif_file: ${{ runner.temp }}/codeql-sarif + category: "/language:${{ matrix.language }}" + # Only a failed upload leaves one: `analyze-retry` uploads what it finds. + # The artifact also keeps the file for whoever looks into a rejection. + - name: Keep the SARIF for the retry + if: steps.sarif.outcome == 'failure' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: codeql-sarif-${{ matrix.language }} + path: ${{ runner.temp }}/codeql-sarif + retention-days: 7 + if-no-files-found: error + + # Uploads once more the results of each language whose upload failed. A matrix + # job's outputs are one set shared by its legs, so the legs cannot say which of + # them failed: the artifacts do. Each carries one SARIF, named by the CodeQL + # language (javascript-typescript's is javascript.sarif), and the steps below + # are one per language. The analysis key is held at the one `analyze` has + # (`CODEQL_ACTION_ANALYSIS_KEY`, which the action reads before it asks the API + # for it), the category is the leg's, and the matrix input is what the leg's + # `toJson(matrix)` is (written out: zizmor reads `toJson(fromJSON(..))` as + # obfuscation), so that the retried upload is the same configuration in code + # scanning and leaves no second one holding alerts the next run closes. + # With nothing to retry the job does a checkout and an empty download. + analyze-retry: + name: Analyze upload retry + needs: analyze + if: ${{ !cancelled() && needs.analyze.result == 'success' }} + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: read # the checkout + actions: read # what `analyze` holds, for codeql-action's calls about the run + security-events: write # the upload to code scanning + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + # A pattern that matches nothing downloads nothing and is not an error; + # `merge-multiple` puts the files of every match in one directory whatever + # their number (one match alone is otherwise not put in a directory of its + # own). + - name: Download the SARIF of each failed upload + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 + with: + pattern: codeql-sarif-* + merge-multiple: true + path: retry + - name: Say which uploads failed + if: hashFiles('retry/*.sarif') != '' + shell: bash + run: | + for file in retry/*.sarif; do + echo "::warning::the upload of CodeQL's results for $(basename "$file" .sarif) to code scanning failed; trying once more" + done + - name: Upload the actions results to code scanning (retry) + if: hashFiles('retry/actions.sarif') != '' + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 + env: + CODEQL_ACTION_ANALYSIS_KEY: .github/workflows/codeql.yml:analyze + with: + sarif_file: retry/actions.sarif + category: /language:actions + matrix: |- + { + "language": "actions" + } + - name: Upload the javascript-typescript results to code scanning (retry) + if: hashFiles('retry/javascript.sarif') != '' + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 + env: + CODEQL_ACTION_ANALYSIS_KEY: .github/workflows/codeql.yml:analyze + with: + sarif_file: retry/javascript.sarif + category: /language:javascript-typescript + matrix: |- + { + "language": "javascript-typescript" + } + - name: Upload the python results to code scanning (retry) + if: hashFiles('retry/python.sarif') != '' + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 + env: + CODEQL_ACTION_ANALYSIS_KEY: .github/workflows/codeql.yml:analyze + with: + sarif_file: retry/python.sarif + category: /language:python + matrix: |- + { + "language": "python" + } + - name: Upload the rust results to code scanning (retry) + if: hashFiles('retry/rust.sarif') != '' + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 + env: + CODEQL_ACTION_ANALYSIS_KEY: .github/workflows/codeql.yml:analyze + with: + sarif_file: retry/rust.sarif + category: /language:rust + matrix: |- + { + "language": "rust" + } From e9a936c60c262c10e07f70be2acf74169ed4a0ce Mon Sep 17 00:00:00 2001 From: Griffin Long <151978260+DrunkOnJava@users.noreply.github.com> Date: Thu, 5 Nov 2026 19:01:24 -0500 Subject: [PATCH 2/3] ci(codeql): upload failure-only, overwrite the kept SARIF, try every language, and fail on a SARIF no step uploads (#1499) Second-read findings on the retry. analyze takes upload: failure-only, not never: both leave the SARIF in output and upload nothing of it (the upload is gated on always), but never makes codeql-action skip the failed-run SARIF when the analysis itself dies. The kept artifact overwrites an earlier attempt's. Each language's retry step runs whatever an earlier one did, a last step fails on a kept SARIF no step covers, the retry job drops actions: read (with the analysis key set upload-sarif never asks about the run), and docs/maintaining.md's CodeQL bullet names the retry job. Closes #1499. --- .github/workflows/codeql.yml | 59 ++++++++++++++++++++++++++++++------ docs/maintaining.md | 10 +++++- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d4643f29b..46345dda8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -77,13 +77,19 @@ jobs: with: languages: ${{ matrix.language }} build-mode: none - # `upload: never` leaves the SARIF in `output`, with the category already - # in it (analyze passes it to the CLI), and uploads nothing. The overlay - # base database on `main` is still saved by this step. + # `upload: failure-only` writes the SARIF to `output`, with the category + # already in it (analyze passes it to the CLI), and does not upload it: at + # the pinned commit analyze sends the results only for `always`, and only + # an upload sets the one-category-per-job sentinel. Unlike `never`, which + # analyze's own action.yml calls not recommended, it keeps codeql-action's + # upload of the diagnostics of a run whose analysis itself fails + # (init-action-post-helper.ts, `prepareFailedSarif`, skips that for + # `never`). The overlay base database on `main` is still saved by this + # step. - uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: category: "/language:${{ matrix.language }}" - upload: never + upload: failure-only output: ${{ runner.temp }}/codeql-sarif # The upload analyze did itself, with the same category. It waits for # processing as analyze did, so a file code scanning rejects fails here. @@ -96,6 +102,9 @@ jobs: category: "/language:${{ matrix.language }}" # Only a failed upload leaves one: `analyze-retry` uploads what it finds. # The artifact also keeps the file for whoever looks into a rejection. + # `overwrite` lets "Re-run all jobs" replace the one an earlier attempt of + # the run left, which would otherwise fail this step (an artifact name is + # taken once per run). - name: Keep the SARIF for the retry if: steps.sarif.outcome == 'failure' uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 @@ -104,6 +113,7 @@ jobs: path: ${{ runner.temp }}/codeql-sarif retention-days: 7 if-no-files-found: error + overwrite: true # Uploads once more the results of each language whose upload failed. A matrix # job's outputs are one set shared by its legs, so the legs cannot say which of @@ -116,6 +126,19 @@ jobs: # obfuscation), so that the retried upload is the same configuration in code # scanning and leaves no second one holding alerts the next run closes. # With nothing to retry the job does a checkout and an empty download. + # + # Each language's step runs whatever an earlier one did (`!cancelled()`), so a + # rejected file does not leave the next language's results unuploaded; the job + # still fails, and so does the run. The last step fails on a SARIF no step + # uploads, so a language added to `analyze`'s matrix and not here cannot pass + # with its kept results unsent. A re-run of the run (all jobs) can find an + # artifact an earlier attempt left and upload that file again: the same commit + # and category replace it. + # + # On a pull request the retried upload skips the post-filter that drops alerts + # outside the diff (`filterAlertsByDiffRange` reads the leg's own RUNNER_TEMP), + # which the first attempt, in the leg's job, applies; the query-time + # `restrictAlertsTo` of the diff-informed analysis still narrows the file. analyze-retry: name: Analyze upload retry needs: analyze @@ -124,7 +147,9 @@ jobs: timeout-minutes: 20 permissions: contents: read # the checkout - actions: read # what `analyze` holds, for codeql-action's calls about the run + # With the analysis key set, the only call upload-sarif makes about the run + # (GET .../actions/runs/{id}, in api-client.ts `getWorkflowRelativePath`) + # is not made, so `actions: read` is not held. security-events: write # the upload to code scanning steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -148,7 +173,7 @@ jobs: echo "::warning::the upload of CodeQL's results for $(basename "$file" .sarif) to code scanning failed; trying once more" done - name: Upload the actions results to code scanning (retry) - if: hashFiles('retry/actions.sarif') != '' + if: ${{ !cancelled() && hashFiles('retry/actions.sarif') != '' }} uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 env: CODEQL_ACTION_ANALYSIS_KEY: .github/workflows/codeql.yml:analyze @@ -160,7 +185,7 @@ jobs: "language": "actions" } - name: Upload the javascript-typescript results to code scanning (retry) - if: hashFiles('retry/javascript.sarif') != '' + if: ${{ !cancelled() && hashFiles('retry/javascript.sarif') != '' }} uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 env: CODEQL_ACTION_ANALYSIS_KEY: .github/workflows/codeql.yml:analyze @@ -172,7 +197,7 @@ jobs: "language": "javascript-typescript" } - name: Upload the python results to code scanning (retry) - if: hashFiles('retry/python.sarif') != '' + if: ${{ !cancelled() && hashFiles('retry/python.sarif') != '' }} uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 env: CODEQL_ACTION_ANALYSIS_KEY: .github/workflows/codeql.yml:analyze @@ -184,7 +209,7 @@ jobs: "language": "python" } - name: Upload the rust results to code scanning (retry) - if: hashFiles('retry/rust.sarif') != '' + if: ${{ !cancelled() && hashFiles('retry/rust.sarif') != '' }} uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 env: CODEQL_ACTION_ANALYSIS_KEY: .github/workflows/codeql.yml:analyze @@ -195,3 +220,19 @@ jobs: { "language": "rust" } + - name: Fail on a SARIF no step above uploads + if: ${{ !cancelled() }} + shell: bash + run: | + shopt -s nullglob + status=0 + for file in retry/*.sarif; do + case "$(basename "$file")" in + actions.sarif | javascript.sarif | python.sarif | rust.sarif) ;; + *) + echo "::error::${file} is kept results that no step of this job uploads: add one for its language, as the steps above do" + status=1 + ;; + esac + done + exit "$status" diff --git a/docs/maintaining.md b/docs/maintaining.md index 2b0b5a69e..4c7b3e670 100644 --- a/docs/maintaining.md +++ b/docs/maintaining.md @@ -837,7 +837,15 @@ Configured on the repository, with the rulesets kept as code in on every push to main, weekly, and on every ready pull request that changes more than documentation (`**.md`, `docs/`, `reports/`, `changelog.d/`): a draft's jobs are skipped and its analysis starts when it is marked ready - (#1457). The default setup stays + (#1457). Each `Analyze ()` job writes its SARIF and uploads it in a + step of its own that may fail without failing the job; a failed upload leaves + the SARIF as the artifact `codeql-sarif-` (7 days), and the job + `Analyze upload retry` uploads what it finds once more, with the leg's + category, analysis key and matrix, in a job of its own because codeql-action + refuses a second upload of one category in a job (#1499). A file code + scanning rejects fails the retry and the run, and so does a kept SARIF whose + language the retry job has no step for: adding a language to the matrix means + adding its upload step there. The default setup stays off: it cannot scan Rust, and GitHub refuses an advanced setup's results while it is on. - Every action is pinned to a full commit SHA and the repository requires it. From 4837ed23d873848c5ef0e00fa68de3f604ec9be0 Mon Sep 17 00:00:00 2001 From: Griffin Long <151978260+DrunkOnJava@users.noreply.github.com> Date: Thu, 5 Nov 2026 19:09:11 -0500 Subject: [PATCH 3/3] ci(codeql): the retry's last step reads the covered SARIFs from the job's own upload steps (#1499) The first version listed the four file names beside the four steps, which a step removed or a name added would leave out of step. It now fails on a kept SARIF whose retry/.sarif no step of the workflow names, checked on fake kept files and on the workflow with a step's file renamed. Closes #1499. --- .github/workflows/codeql.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 46345dda8..d3912ca58 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -220,6 +220,9 @@ jobs: { "language": "rust" } + # A kept SARIF is covered when this file has a step that uploads it, which + # the checkout above holds (the workflow of this run: a pull request's + # merge commit is both). - name: Fail on a SARIF no step above uploads if: ${{ !cancelled() }} shell: bash @@ -227,12 +230,9 @@ jobs: shopt -s nullglob status=0 for file in retry/*.sarif; do - case "$(basename "$file")" in - actions.sarif | javascript.sarif | python.sarif | rust.sarif) ;; - *) - echo "::error::${file} is kept results that no step of this job uploads: add one for its language, as the steps above do" - status=1 - ;; - esac + if ! grep -qF "sarif_file: ${file}" .github/workflows/codeql.yml; then + echo "::error::${file} is kept results that no step of this job uploads: add one for its language, as the steps above do" + status=1 + fi done exit "$status"