diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e245f9f..10bc84c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,17 +3,81 @@ on: push: branches: - master - paths-ignore: - - '**.md' - - '**.asc' pull_request: - paths-ignore: - - '**.md' - - '**.asc' env: PGUSER: postgres jobs: + # Cheap gate that lets the heavy jobs below skip themselves on commits + # that touch only docs. This must run on every push/pull_request (no + # paths-ignore on the workflow itself), otherwise the required + # all-checks-passed check would never report on doc-only pushes and get + # stuck Pending in branch protection. + changes: + name: 🔍 Detect docs-only changes + runs-on: ubuntu-latest + outputs: + docs_only: ${{ steps.diff.outputs.docs_only }} + steps: + - name: Check out the repo + uses: actions/checkout@v6 + with: + # Full history needed so BASE and HEAD below are both reachable + # for `git diff`. + fetch-depth: 0 + - name: Compute per-push changed files + id: diff + run: | + if [ "${{ github.event_name }}" = "pull_request" ] && \ + [ "${{ github.event.action }}" = "synchronize" ] && \ + [ -n "${{ github.event.before }}" ]; then + # A push to an already-open PR: before/after give the true + # per-push diff, same as for a branch push. + BASE="${{ github.event.before }}" + HEAD="${{ github.event.after }}" + elif [ "${{ github.event_name }}" = "pull_request" ]; then + # First run for this PR (opened/reopened/etc, or synchronize + # without a usable before): fall back to the whole base...head + # diff. + BASE="${{ github.event.pull_request.base.sha }}" + HEAD="${{ github.event.pull_request.head.sha }}" + else + BASE="${{ github.event.before }}" + HEAD="${{ github.event.after }}" + fi + + echo "base=$BASE" + echo "head=$HEAD" + + # Fail-safe: a missing HEAD, or an all-zeros BASE (e.g. a new + # branch's first push, where GitHub reports no prior commit), + # means we can't compute a real diff. Run the full matrix rather + # than risk skipping tests. + if [ -z "$HEAD" ] || [ -z "$BASE" ] || [[ "$BASE" =~ ^0+$ ]]; then + echo "docs_only=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + CHANGED=$(git diff --name-only "$BASE" "$HEAD" || echo __DIFF_FAILED__) + + DOCS_ONLY=true + if [ "$CHANGED" = "__DIFF_FAILED__" ] || [ -z "$CHANGED" ]; then + DOCS_ONLY=false + else + while IFS= read -r f; do + if ! [[ "$f" =~ \.(md|asc)$ ]]; then + DOCS_ONLY=false + break + fi + done <<< "$CHANGED" + fi + + echo "changed files:" + echo "$CHANGED" + echo "docs_only=$DOCS_ONLY" >> "$GITHUB_OUTPUT" + test: + needs: [changes] + if: needs.changes.outputs.docs_only != 'true' strategy: matrix: pg: [18, 17, 16, 15, 14, 13, 12, 11, 10] @@ -31,6 +95,8 @@ jobs: run: make test pg-upgrade-test: + needs: [changes] + if: needs.changes.outputs.docs_only != 'true' strategy: matrix: include: @@ -117,6 +183,8 @@ jobs: # we cannot pg_upgrade from a cluster that has them installed. extension-update-test: + needs: [changes] + if: needs.changes.outputs.docs_only != 'true' strategy: matrix: # Restricted to PG10 only because the pre-0.2.2 install scripts use @@ -173,10 +241,10 @@ jobs: # protection rules. Matrix jobs produce check names like "🐘 PostgreSQL 14" # which would all need to be listed individually and updated whenever the # matrix changes. This job passes if all others passed or were skipped (e.g. - # on a docs-only push with paths-ignore), and fails if any failed or were - # cancelled. + # the heavy jobs gated off by the `changes` job on a docs-only push), and + # fails if any failed or were cancelled. all-checks-passed: - needs: [test, pg-upgrade-test, extension-update-test] + needs: [changes, test, pg-upgrade-test, extension-update-test] if: always() runs-on: ubuntu-latest steps: diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index fc7e26c..3c794dd 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -78,12 +78,24 @@ jobs: if: steps.gate.outputs.decision == 'run' # Intentionally tracks the major-version tag (not a pinned SHA) so # upstream fixes are picked up automatically. - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 1 persist-credentials: false + # actions/checkout added a guard that refuses to check out fork + # PR heads under pull_request_target/workflow_run by default + # (a "pwn request" mitigation), with this escape hatch for + # vetted cases where that's intentional. Originally shipped in + # v7.0.0, since backported to the v4/v5/v6 floating major tags + # too (confirmed against actions/checkout's live action.yml on + # each), so no major-version bump is needed to use it here. + # Safe here: gated to the trusted fork owner above, read-only + # (persist-credentials: false), and this workflow file itself + # always comes from master under pull_request_target, so a PR + # can't alter the job that reviews it. + allow-unsafe-pr-checkout: true - name: Run Claude Code Review if: steps.gate.outputs.decision == 'run' diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index c85ec00..9c9cdb6 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -31,7 +31,7 @@ jobs: - name: Checkout repository # Intentionally tracks the major-version tag (not a pinned SHA) so # upstream fixes are picked up automatically. - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 1 persist-credentials: false diff --git a/CLAUDE.md b/CLAUDE.md index 05e77e2..01e7d49 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ After **every** push, monitor GitHub CI in a background subagent until all jobs pass or a failure is confirmed. Use `gh pr checks --watch` when the branch has an open PR; otherwise (a branch with no PR yet, or a push to `master`) use `gh run watch` for the pushed commit. Investigate and fix failures immediately rather than leaving them for the user to notice. -(`paths-ignore` in `.github/workflows/ci.yml` skips CI only when the *entire* change set is docs-only — e.g. a docs-only push to `master`, or a PR whose whole diff is `**.md`/`**.asc`. A docs-only commit on a PR that also touches code still triggers CI on the full PR diff. When unsure, check `gh run list` for the pushed commit and monitor whatever run appears; if none does, there is nothing to watch.) +(`.github/workflows/ci.yml`'s `changes` job computes the actual per-push changed file set and exposes `docs_only`; the heavy `test`, `pg-upgrade-test`, and `extension-update-test` jobs skip when `needs.changes.outputs.docs_only == 'true'` — i.e. every changed file in that push matches `**.md`/`**.asc`. Unlike the old workflow-level `paths-ignore`, this is evaluated per push/commit, not over the whole PR diff, so a doc-only commit on a PR that also touches code still gets the skip, and the workflow (including the required `all-checks-passed` check) always triggers and reports rather than being skipped outright by GitHub. When unsure, check `gh run list` for the pushed commit and monitor whatever run appears; if none does, there is nothing to watch.) ## Build/test system (pgxntool)