CI: gate heavy reviews on self-review (Doc consistency); comment-trigger; disable TTL heavy reviews #159

Open
rodin wants to merge 8 commits from ci-selfreview-gate into main
Showing only changes of commit b716aed914 - Show all commits
+23 -9
View File
@@ -5,6 +5,8 @@ on:
branches: [main]
pull_request:
types: [opened, synchronize]
issue_comment:
types: [created, edited]
env:
SELF_REVIEW_TTL_MIN: '45'
@@ -12,6 +14,7 @@ env:
jobs:
test:
runs-on: ubuntu-24.04
Review

[MAJOR] The test job is restricted to only pull_request events via if: github.event_name == 'pull_request', so tests no longer run on push to main even though the workflow still triggers on push. This likely degrades CI coverage and branch protection; prefer skipping only issue_comment runs or include push explicitly (e.g., if: github.event_name != 'issue_comment' or if: github.event_name == 'pull_request' || github.event_name == 'push').

**[MAJOR]** The test job is restricted to only pull_request events via `if: github.event_name == 'pull_request'`, so tests no longer run on push to main even though the workflow still triggers on push. This likely degrades CI coverage and branch protection; prefer skipping only `issue_comment` runs or include push explicitly (e.g., `if: github.event_name != 'issue_comment'` or `if: github.event_name == 'pull_request' || github.event_name == 'push'`).
if: github.event_name == 'pull_request'
Review

[MINOR] The test job is restricted to run only on pull_request events (if: github.event_name == 'pull_request'), so tests no longer run on push to main despite the workflow being triggered for pushes. If this was not intended, adjust the condition (e.g., exclude only issue_comment) to keep tests on push events.

**[MINOR]** The test job is restricted to run only on pull_request events (if: github.event_name == 'pull_request'), so tests no longer run on push to main despite the workflow being triggered for pushes. If this was not intended, adjust the condition (e.g., exclude only issue_comment) to keep tests on push events.
steps:
Review

[NIT] The test job installs jq even though it is unused in that job (jq is only needed in review-gate and review). Removing this step will speed up runs slightly.

**[NIT]** The test job installs jq even though it is unused in that job (jq is only needed in review-gate and review). Removing this step will speed up runs slightly.
- uses: actions/checkout@v4
Review

[MINOR] The test job installs jq but does not use it in any subsequent step, adding unnecessary time to test runs. Remove if not needed.

**[MINOR]** The test job installs jq but does not use it in any subsequent step, adding unnecessary time to test runs. Remove if not needed.
- uses: actions/setup-go@v5
@@ -23,7 +26,7 @@ jobs:
review-gate:
runs-on: ubuntu-24.04
Review

[MINOR] issue_comment events will trigger the review-gate job for any PR comment, allowing an attacker to spam comments to repeatedly invoke the job and consume runner resources (DoS). Although heavy reviewers won't run without a valid self-review, the gate still performs package installs and API calls on each comment.

**[MINOR]** issue_comment events will trigger the review-gate job for any PR comment, allowing an attacker to spam comments to repeatedly invoke the job and consume runner resources (DoS). Although heavy reviewers won't run without a valid self-review, the gate still performs package installs and API calls on each comment.
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.issue.pull_request)
outputs:
Review

[MINOR] The test job now installs jq but never uses it. jq is only needed in the review-gate and review jobs. This adds ~5s to every test run for no reason.

**[MINOR]** The `test` job now installs `jq` but never uses it. `jq` is only needed in the `review-gate` and `review` jobs. This adds ~5s to every test run for no reason.
allow_review: ${{ steps.gate.outputs.allow_review }}
security-review-bot marked this conversation as resolved
Review

[MINOR] The review-gate job is triggered on issue_comment events for PRs, which is intended. For defense-in-depth, consider explicitly setting permissions: in the workflow to least-privilege (e.g., read-only) to reduce blast radius for the default token.

**[MINOR]** The review-gate job is triggered on `issue_comment` events for PRs, which is intended. For defense-in-depth, consider explicitly setting `permissions:` in the workflow to least-privilege (e.g., read-only) to reduce blast radius for the default token.
Review

[MINOR] Condition uses (github.event.issue.pull_request) directly. For clarity and future-proofing, prefer an explicit null check (e.g., github.event.issue.pull_request != null) to avoid truthiness ambiguity.

**[MINOR]** Condition uses (github.event.issue.pull_request) directly. For clarity and future-proofing, prefer an explicit null check (e.g., github.event.issue.pull_request != null) to avoid truthiness ambiguity.
reason: ${{ steps.gate.outputs.reason }}
1
@@ -35,19 +38,23 @@ jobs:
run: |
set -e
REPO=${{ github.repository }}
PR=${{ github.event.pull_request.number }}
SHA=${{ github.event.pull_request.head.sha }}
TTL_MIN=${SELF_REVIEW_TTL_MIN}
API="${{ github.server_url }}/api/v1"
if [ "${GITHUB_EVENT_NAME}" = "issue_comment" ]; then
PR=${{ github.event.issue.number }}
Review

[MINOR] The AGE_MIN variable is computed and ALLOW=true/REASON=ttl is set when TTL is exceeded, but allow_review=true with reason=ttl does NOT trigger the review job (the review job gates on reason == 'self-review'). This is correct per the stated intent, but the allow_review output is never actually consumed by any job — only reason is checked. The allow_review output is dead output and may cause future confusion. Consider removing it or documenting its lack of use.

**[MINOR]** The AGE_MIN variable is computed and ALLOW=true/REASON=ttl is set when TTL is exceeded, but `allow_review=true` with `reason=ttl` does NOT trigger the review job (the review job gates on `reason == 'self-review'`). This is correct per the stated intent, but the `allow_review` output is never actually consumed by any job — only `reason` is checked. The `allow_review` output is dead output and may cause future confusion. Consider removing it or documenting its lack of use.
Review

[MINOR] The review-gate script uses set -e but not -u or -o pipefail. Adding set -euo pipefail would harden error handling and ensure failures in pipelines or unset variables don’t lead to unintended behavior (fail closed).

**[MINOR]** The review-gate script uses `set -e` but not `-u` or `-o pipefail`. Adding `set -euo pipefail` would harden error handling and ensure failures in pipelines or unset variables don’t lead to unintended behavior (fail closed).
Review

[MINOR] Shell robustness: consider using 'set -euo pipefail' and 'curl -fsS' to fail fast on unset vars and HTTP errors. Currently 'set -e' won't catch all error modes, and curl without -f treats HTTP 4xx/5xx as success.

**[MINOR]** Shell robustness: consider using 'set -euo pipefail' and 'curl -fsS' to fail fast on unset vars and HTTP errors. Currently 'set -e' won't catch all error modes, and curl without -f treats HTTP 4xx/5xx as success.
else
PR=${{ github.event.pull_request.number }}
fi
# Get head SHA from PR JSON (works for both events)
Review

[MINOR] The review-gate condition (github.event_name == 'issue_comment' && github.event.issue.pull_request) assumes object truthiness. For robustness, explicitly check existence (e.g., github.event.issue.pull_request != null) to avoid edge-case evaluation issues.

**[MINOR]** The review-gate condition `(github.event_name == 'issue_comment' && github.event.issue.pull_request)` assumes object truthiness. For robustness, explicitly check existence (e.g., `github.event.issue.pull_request != null`) to avoid edge-case evaluation issues.
PR_JSON=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/pulls/$PR")
SHA=$(echo "$PR_JSON" | jq -r .head.sha)
UPDATED_AT=$(echo "$PR_JSON" | jq -r .updated_at)
NOW=$(date -u +%s)
Review

[MINOR] Shell variables from GitHub expressions are interpolated directly without quoting: REPO=${{ github.repository }}. If the repository name contained spaces or special characters this would break. Prefer REPO='${{ github.repository }}' or export with quotes for robustness.

**[MINOR]** Shell variables from GitHub expressions are interpolated directly without quoting: `REPO=${{ github.repository }}`. If the repository name contained spaces or special characters this would break. Prefer `REPO='${{ github.repository }}'` or export with quotes for robustness.
PR_TS=$(date -u -d "$UPDATED_AT" +%s)
security-review-bot marked this conversation as resolved
Review

[MINOR] Curl invocations lack explicit failure and timeout settings. Consider adding --fail (or --fail-with-body), --max-time, and/or --retry to prevent hanging jobs and reduce DOS potential against CI if the API stalls.

**[MINOR]** Curl invocations lack explicit failure and timeout settings. Consider adding `--fail` (or `--fail-with-body`), `--max-time`, and/or `--retry` to prevent hanging jobs and reduce DOS potential against CI if the API stalls.
AGE_MIN=$(( (NOW - PR_TS) / 60 ))
TTL_MIN=${SELF_REVIEW_TTL_MIN}
Review

[MINOR] The jq regex (?im)^###\s+Doc consistency\b is passed through multiple layers of shell quoting and JSON encoding before reaching jq. The \b word-boundary assertion and the multiline mode should work in jq's ONIG engine, but this is non-obvious and fragile. A simpler test("(?m)^### Doc consistency") without the \b would be more readable and equally correct for this use case.

**[MINOR]** The jq regex `(?im)^###\s+Doc consistency\b` is passed through multiple layers of shell quoting and JSON encoding before reaching jq. The `\b` word-boundary assertion and the multiline mode should work in jq's ONIG engine, but this is non-obvious and fragile. A simpler `test("(?m)^### Doc consistency")` without the `\b` would be more readable and equally correct for this use case.
COMMENTS=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/issues/$PR/comments?limit=200")
HAS_SR=$(echo "$COMMENTS" | jq -r --arg sha "$SHA" '[.[] | select(.user.login=="rodin") | select(.body|contains("Self-review against "+$sha)) | select(.body|test("(?im)^###\\s+Doc consistency\\b"))] | length')
HAS_SR=$(echo "$COMMENTS" | jq -r --arg sha "$SHA" '[.[] | select(.user.login=="rodin") | select(.body|contains("Self-review against "+$sha)) | select(.body|test("(?im)^###\s+Doc consistency\b"))] | length')
if [ "$HAS_SR" -gt 0 ]; then
ALLOW=true
security-review-bot marked this conversation as resolved
Review

[MINOR] Same as above: the comments API request should include curl timeouts and --fail to avoid indefinite hangs and fail closed on HTTP errors.

**[MINOR]** Same as above: the comments API request should include curl timeouts and `--fail` to avoid indefinite hangs and fail closed on HTTP errors.
Review

[NIT] Comments API call limits results to 200. Very large PRs with more than 200 comments could miss earlier valid self-reviews. Consider pagination or filtering by most recent comments if this is a concern.

**[NIT]** Comments API call limits results to 200. Very large PRs with more than 200 comments could miss earlier valid self-reviews. Consider pagination or filtering by most recent comments if this is a concern.
3
@@ -65,8 +72,8 @@ jobs:
review:
runs-on: ubuntu-24.04
Review

[MINOR] The TTL is calculated using PR_JSON.updated_at, which changes on every push/comment. A PR with a new comment 5 minutes ago will never pass the TTL gate regardless of how old the PR is. If the intent is 'PR was opened and nobody reviewed it for 45 minutes', created_at would be more appropriate. If the intent is 'last activity was 45 minutes ago', the current behavior is correct but differs from the PR description which says 'TTL keeps the gate green'.

**[MINOR]** The TTL is calculated using `PR_JSON.updated_at`, which changes on every push/comment. A PR with a new comment 5 minutes ago will never pass the TTL gate regardless of how old the PR is. If the intent is 'PR was opened and nobody reviewed it for 45 minutes', `created_at` would be more appropriate. If the intent is 'last activity was 45 minutes ago', the current behavior is correct but differs from the PR description which says 'TTL keeps the gate green'.
if: github.event_name == 'pull_request' && needs.review-gate.outputs.reason == 'self-review'
needs: [test, review-gate]
if: needs.review-gate.outputs.reason == 'self-review' && (github.event_name == 'pull_request' || github.event_name == 'issue_comment')
needs: [review-gate]
Review

[MINOR] The allow_review output is set but never consumed — review.if only checks reason, not allow_review. The output could be removed, or the review.if condition could be simplified to just needs.review-gate.outputs.reason == 'self-review' (which it already is). The unused output is harmless but misleading.

**[MINOR]** The `allow_review` output is set but never consumed — `review.if` only checks `reason`, not `allow_review`. The output could be removed, or the `review.if` condition could be simplified to just `needs.review-gate.outputs.reason == 'self-review'` (which it already is). The unused output is harmless but misleading.
strategy:
matrix:
include:
1
@@ -88,11 +95,18 @@ jobs:
with:
go-version: '1.26'
- run: go build -o review-bot ./cmd/review-bot
- name: Set PR_NUMBER for event type
run: |
if [ "${GITHUB_EVENT_NAME}" = "issue_comment" ]; then
echo "PR_NUMBER=${{ github.event.issue.number }}" >> $GITHUB_ENV
else
echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV
fi
- name: Run ${{ matrix.name }} review
env:
VCS_URL: ${{ github.server_url }}
GITEA_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_NUMBER: ${{ env.PR_NUMBER }}
REVIEWER_TOKEN: ${{ secrets[matrix.token_secret] }}
REVIEWER_NAME: ${{ matrix.name }}
LLM_PROVIDER: aicore