From 9d7eb453049ac5a1c48f469c22baf4df5c9cd482 Mon Sep 17 00:00:00 2001 From: Balaji Ganesan Date: Sat, 18 Jul 2026 21:12:13 -0700 Subject: [PATCH 1/4] ci: run CodeQL only for the languages a PR changes The Go CodeQL job autobuilds the whole umbrella (~25 min). Today both the go and rust legs run on every code PR, so a Rust-only PR pays the full Go build for nothing. Add a detect job (GitHub API, no third-party actions per the org allowlist) that gates each language leg: on a PR only the changed language runs; pushes to main and the weekly schedule still run both for a complete baseline. A change to codeql.yml re-scans both. Go stays build-mode autobuild: CodeQL 2.26 confirmed Go does not support build-mode none. Co-authored-by: Balaji Ganesan Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/codeql.yml | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 18ae8cf3e..971f757a4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,8 +40,45 @@ permissions: pull-requests: write jobs: + detect: + name: Detect changed languages + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + outputs: + go: ${{ steps.f.outputs.go }} + rust: ${{ steps.f.outputs.rust }} + steps: + - name: Detect changed languages + id: f + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + if [ "${{ github.event_name }}" = "pull_request" ]; then + files="$(gh api --paginate "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --jq '.[].filename' || true)" + else + files="" + fi + go=false; rust=false + if [ "${{ github.event_name }}" != "pull_request" ]; then go=true; rust=true; fi + printf '%s\n' "$files" | grep -qE '(\.go|/go\.mod|/go\.sum)$' && go=true || true + printf '%s\n' "$files" | grep -qE '(\.rs|/Cargo\.toml|/Cargo\.lock)$' && rust=true || true + # A change to the scan config itself re-scans both languages. + printf '%s\n' "$files" | grep -qE '\.github/workflows/codeql\.yml$' && { go=true; rust=true; } || true + echo "go=$go" >> "$GITHUB_OUTPUT" + echo "rust=$rust" >> "$GITHUB_OUTPUT" + echo "changed-languages: go=$go rust=$rust" + analyze: name: CodeQL (${{ matrix.language }}) + needs: detect + # On a PR, only run the language(s) that actually changed: a Rust-only PR + # skips the expensive Go autobuild, and a Go-only PR skips Rust. Pushes to + # main and the weekly schedule always run both for a complete baseline. + if: github.event_name != 'pull_request' || needs.detect.outputs[matrix.language] == 'true' runs-on: ubuntu-latest strategy: # Keep one language's failure from cancelling the others. From 13b9f1012b24a2d09b1510aa08b36581f48cfd24 Mon Sep 17 00:00:00 2001 From: Balaji Ganesan Date: Sat, 18 Jul 2026 21:24:48 -0700 Subject: [PATCH 2/4] ci: fix startup failure - use a dynamic matrix instead of matrix in job if matrix context is not available in a job-level if, which caused a workflow startup failure. Have the detect job emit the matrix JSON (only the changed languages) and gate analyze on a simple needs output instead. Co-authored-by: Balaji Ganesan Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/codeql.yml | 39 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 971f757a4..a192b0c13 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,6 +40,11 @@ permissions: pull-requests: write jobs: + # Decide which languages to analyze. On a PR, only the language(s) whose files + # changed run (a Rust-only PR skips the ~25 min Go autobuild, and vice versa). + # Pushes to main and the weekly schedule scan both. Emits a dynamic matrix so + # no job-level `if` referencing `matrix` is needed (that context is not allowed + # in a job `if`). Uses the GitHub API, not a third-party action (org allowlist). detect: name: Detect changed languages runs-on: ubuntu-latest @@ -47,8 +52,8 @@ jobs: contents: read pull-requests: read outputs: - go: ${{ steps.f.outputs.go }} - rust: ${{ steps.f.outputs.rust }} + matrix: ${{ steps.f.outputs.matrix }} + any: ${{ steps.f.outputs.any }} steps: - name: Detect changed languages id: f @@ -64,33 +69,29 @@ jobs: fi go=false; rust=false if [ "${{ github.event_name }}" != "pull_request" ]; then go=true; rust=true; fi - printf '%s\n' "$files" | grep -qE '(\.go|/go\.mod|/go\.sum)$' && go=true || true - printf '%s\n' "$files" | grep -qE '(\.rs|/Cargo\.toml|/Cargo\.lock)$' && rust=true || true + if printf '%s\n' "$files" | grep -qE '(\.go|/go\.mod|/go\.sum)$'; then go=true; fi + if printf '%s\n' "$files" | grep -qE '(\.rs|/Cargo\.toml|/Cargo\.lock)$'; then rust=true; fi # A change to the scan config itself re-scans both languages. - printf '%s\n' "$files" | grep -qE '\.github/workflows/codeql\.yml$' && { go=true; rust=true; } || true - echo "go=$go" >> "$GITHUB_OUTPUT" - echo "rust=$rust" >> "$GITHUB_OUTPUT" + if printf '%s\n' "$files" | grep -qE '\.github/workflows/codeql\.yml$'; then go=true; rust=true; fi + inc="" + if [ "$go" = true ]; then inc="${inc}{\"language\":\"go\",\"build-mode\":\"autobuild\"},"; fi + if [ "$rust" = true ]; then inc="${inc}{\"language\":\"rust\",\"build-mode\":\"none\"},"; fi + inc="${inc%,}" + echo "matrix={\"include\":[${inc}]}" >> "$GITHUB_OUTPUT" + if [ -n "$inc" ]; then echo "any=true" >> "$GITHUB_OUTPUT"; else echo "any=false" >> "$GITHUB_OUTPUT"; fi echo "changed-languages: go=$go rust=$rust" analyze: name: CodeQL (${{ matrix.language }}) needs: detect - # On a PR, only run the language(s) that actually changed: a Rust-only PR - # skips the expensive Go autobuild, and a Go-only PR skips Rust. Pushes to - # main and the weekly schedule always run both for a complete baseline. - if: github.event_name != 'pull_request' || needs.detect.outputs[matrix.language] == 'true' + if: needs.detect.outputs.any == 'true' runs-on: ubuntu-latest strategy: # Keep one language's failure from cancelling the others. fail-fast: false - matrix: - include: - # Go is a traced language and does not support build-mode none, so it - # autobuilds. Rust has no autobuilder and uses buildless extraction. - - language: go - build-mode: autobuild - - language: rust - build-mode: none + # Only the languages detect selected. Go must autobuild (CodeQL 2.26 + # confirmed Go does not support build-mode none); Rust uses buildless. + matrix: ${{ fromJSON(needs.detect.outputs.matrix) }} steps: - name: Checkout repository uses: actions/checkout@v4 From 1c061aef4878ff392e4655e9d0864ed6290441d7 Mon Sep 17 00:00:00 2001 From: Balaji Ganesan Date: Sun, 19 Jul 2026 09:21:13 -0700 Subject: [PATCH 3/4] ci: scan Go on main and weekly only; PRs scan buildless languages Go is the only language in the stack whose CodeQL analysis requires a traced build (build-mode none is supported for C/C++, C#, Java, and Rust, but not Go), and its autobuild compiles the whole umbrella (~21 min) for advisory-only PR feedback (fail-on-findings is false). Exclude Go from PR scans: every push to main and the weekly schedule still scan Go fully, so the security baseline is unchanged and detection moves from pre-merge to at-merge. Rust (buildless, ~2.5 min) keeps scanning on PRs, and Java can join as build-mode none when it lands. Co-authored-by: Balaji Ganesan Co-authored-by: Claude Fable 5 --- .github/workflows/codeql.yml | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a192b0c13..191e6c781 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,11 +40,14 @@ permissions: pull-requests: write jobs: - # Decide which languages to analyze. On a PR, only the language(s) whose files - # changed run (a Rust-only PR skips the ~25 min Go autobuild, and vice versa). - # Pushes to main and the weekly schedule scan both. Emits a dynamic matrix so - # no job-level `if` referencing `matrix` is needed (that context is not allowed - # in a job `if`). Uses the GitHub API, not a third-party action (org allowlist). + # Decide which languages to analyze. PR policy: only buildless languages scan + # pre-merge (today Rust; Java joins as build-mode none when it lands). Go is + # excluded on PRs: its traced autobuild compiles the whole umbrella (~20+ min) + # for advisory-only feedback (fail-on-findings is false). Go coverage comes + # from every push to main and the weekly schedule, which scan everything. + # Emits a dynamic matrix so no job-level `if` referencing `matrix` is needed + # (that context is not allowed in a job `if`). Uses the GitHub API, not a + # third-party action (org allowlist). detect: name: Detect changed languages runs-on: ubuntu-latest @@ -68,18 +71,21 @@ jobs: files="" fi go=false; rust=false - if [ "${{ github.event_name }}" != "pull_request" ]; then go=true; rust=true; fi - if printf '%s\n' "$files" | grep -qE '(\.go|/go\.mod|/go\.sum)$'; then go=true; fi - if printf '%s\n' "$files" | grep -qE '(\.rs|/Cargo\.toml|/Cargo\.lock)$'; then rust=true; fi - # A change to the scan config itself re-scans both languages. - if printf '%s\n' "$files" | grep -qE '\.github/workflows/codeql\.yml$'; then go=true; rust=true; fi + if [ "${{ github.event_name }}" != "pull_request" ]; then + # Pushes to main and the weekly schedule scan everything, Go included. + go=true; rust=true + else + if printf '%s\n' "$files" | grep -qE '(\.rs|/Cargo\.toml|/Cargo\.lock)$'; then rust=true; fi + # A change to the scan config itself re-scans the PR-time languages. + if printf '%s\n' "$files" | grep -qE '\.github/workflows/codeql\.yml$'; then rust=true; fi + fi inc="" if [ "$go" = true ]; then inc="${inc}{\"language\":\"go\",\"build-mode\":\"autobuild\"},"; fi if [ "$rust" = true ]; then inc="${inc}{\"language\":\"rust\",\"build-mode\":\"none\"},"; fi inc="${inc%,}" echo "matrix={\"include\":[${inc}]}" >> "$GITHUB_OUTPUT" if [ -n "$inc" ]; then echo "any=true" >> "$GITHUB_OUTPUT"; else echo "any=false" >> "$GITHUB_OUTPUT"; fi - echo "changed-languages: go=$go rust=$rust" + echo "selected languages: go=$go rust=$rust" analyze: name: CodeQL (${{ matrix.language }}) From 620cc2185ef9ce10804802a19e354bd765d89797 Mon Sep 17 00:00:00 2001 From: Balaji Ganesan Date: Mon, 20 Jul 2026 17:55:31 -0700 Subject: [PATCH 4/4] ci(codeql): harden change detection and exclude docs Address review feedback on the per-language gating: - Do not swallow the changed-file lookup failure. Previously `gh api ... || true` left `files` empty on an API error, which the PR path treated as "no code changed" and silently skipped both scans. Now a lookup failure falls back to scanning all languages (fail-open), so a transient error never drops coverage. - Match repository-root manifests. gh api filenames are repo-relative with no leading slash, so anchor Cargo.toml/Cargo.lock (and the workflow self-match) with (^|/) to catch both root and nested files. - Do not run CodeQL on docs-only changes. Drop the broad "src/**" path trigger; code is already covered by the *.go/*.rs + manifest globs, so a Markdown or other non-code change under src/ no longer starts the workflow. Co-authored-by: Balaji Ganesan Co-Authored-By: Claude Opus 4.8 --- .github/workflows/codeql.yml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 191e6c781..803bb3cf2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -7,7 +7,6 @@ on: # migrations, and other non-code PRs skip the scan (the Go autobuild is # expensive). The weekly schedule still does a full unconditional scan. paths: - - "src/**" - "**/*.go" - "**/*.rs" - "**/go.mod" @@ -18,7 +17,6 @@ on: pull_request: branches: [main] paths: - - "src/**" - "**/*.go" - "**/*.rs" - "**/go.mod" @@ -65,19 +63,23 @@ jobs: shell: bash run: | set -euo pipefail - if [ "${{ github.event_name }}" = "pull_request" ]; then - files="$(gh api --paginate "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --jq '.[].filename' || true)" - else - files="" - fi go=false; rust=false + files="" if [ "${{ github.event_name }}" != "pull_request" ]; then # Pushes to main and the weekly schedule scan everything, Go included. go=true; rust=true else - if printf '%s\n' "$files" | grep -qE '(\.rs|/Cargo\.toml|/Cargo\.lock)$'; then rust=true; fi + # Never silently skip: if the changed-file lookup fails, scan both + # languages instead of treating an empty result as "no code changed". + if ! files="$(gh api --paginate "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --jq '.[].filename')"; then + echo "::warning::changed-file lookup failed; scanning all languages" + go=true; rust=true; files="" + fi + # gh api filenames are repo-relative with no leading slash, so anchor + # manifests with (^|/) to match root and nested files alike. + if printf '%s\n' "$files" | grep -qE '\.rs$|(^|/)Cargo\.(toml|lock)$'; then rust=true; fi # A change to the scan config itself re-scans the PR-time languages. - if printf '%s\n' "$files" | grep -qE '\.github/workflows/codeql\.yml$'; then rust=true; fi + if printf '%s\n' "$files" | grep -qE '(^|/)\.github/workflows/codeql\.yml$'; then rust=true; fi fi inc="" if [ "$go" = true ]; then inc="${inc}{\"language\":\"go\",\"build-mode\":\"autobuild\"},"; fi