fix: resolve AI review false positives and address high/medium issues

Root cause of false-positive "critical" errors:
- sed pattern was matching api_key/token within YAML variable names
  (e.g. OLLAMA_API_KEY:) and redacting the ${{ secrets.X }} value,
  producing mangled syntax that confused the AI reviewer
- Fix: use [^$[:space:]] to skip values starting with $ (template
  expressions and shell variable references)

Other fixes:
- Replace --retry-all-errors with --retry-connrefused --retry-max-time 120
  to avoid wasting retries on unrecoverable 4xx errors
- Check HTTP_CODE before jq validation so error messages are meaningful
- Add permissions: pull-requests: write to job
- Add edited to pull_request.types so title changes trigger re-review
- Change git diff .. to git diff ... (three-dot merge-base diff)
- Replace hardcoded server/repo URLs with github.server_url and
  github.repository context variables (portability)
- Log review length before posting to detect truncation
This commit is contained in:
Shaun Arman 2026-04-12 17:02:39 -05:00
parent 1a4c6df6c9
commit 82aae00858

View File

@ -2,7 +2,7 @@ name: PR Review Automation
on: on:
pull_request: pull_request:
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened, edited]
concurrency: concurrency:
group: pr-review-${{ github.event.pull_request.number }} group: pr-review-${{ github.event.pull_request.number }}
@ -11,6 +11,8 @@ concurrency:
jobs: jobs:
review: review:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
pull-requests: write
container: container:
image: ubuntu:22.04 image: ubuntu:22.04
options: --dns 8.8.8.8 --dns 1.1.1.1 options: --dns 8.8.8.8 --dns 1.1.1.1
@ -23,10 +25,13 @@ jobs:
- name: Checkout code - name: Checkout code
shell: bash shell: bash
env:
SERVER_URL: ${{ github.server_url }}
REPOSITORY: ${{ github.repository }}
run: | run: |
set -euo pipefail set -euo pipefail
git init git init
git remote add origin https://gogs.tftsr.com/sarman/tftsr-devops_investigation.git git remote add origin "${SERVER_URL}/${REPOSITORY}.git"
git fetch --depth=1 origin ${{ github.head_ref }} git fetch --depth=1 origin ${{ github.head_ref }}
git checkout FETCH_HEAD git checkout FETCH_HEAD
@ -36,7 +41,7 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
git fetch origin ${{ github.base_ref }} git fetch origin ${{ github.base_ref }}
git diff origin/${{ github.base_ref }}..HEAD > /tmp/pr_diff.txt git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr_diff.txt
echo "diff_size=$(wc -l < /tmp/pr_diff.txt | tr -d ' ')" >> $GITHUB_OUTPUT echo "diff_size=$(wc -l < /tmp/pr_diff.txt | tr -d ' ')" >> $GITHUB_OUTPUT
- name: Analyze with Ollama - name: Analyze with Ollama
@ -54,7 +59,7 @@ jobs:
echo "WARNING: Binary file changes detected — they will be excluded from analysis" echo "WARNING: Binary file changes detected — they will be excluded from analysis"
fi fi
DIFF_CONTENT=$(head -n 500 /tmp/pr_diff.txt \ DIFF_CONTENT=$(head -n 500 /tmp/pr_diff.txt \
| sed -E 's/(password|token|secret|api_key|private_key)[[:space:]]*[=:][[:space:]]*\S+/\1=[REDACTED]/gi') | sed -E 's/(password|token|secret|api_key|private_key)[[:space:]]*[=:][[:space:]]+([^$[:space:]][^[:space:]]*)/\1=[REDACTED]/gi')
PROMPT="Analyze the following code changes for correctness, security issues, and best practices. PR Title: ${PR_TITLE}\n\nDiff:\n${DIFF_CONTENT}\n\nProvide a review with: 1) Summary, 2) Bugs/errors, 3) Security issues, 4) Best practices. Give specific comments with suggested fixes." PROMPT="Analyze the following code changes for correctness, security issues, and best practices. PR Title: ${PR_TITLE}\n\nDiff:\n${DIFF_CONTENT}\n\nProvide a review with: 1) Summary, 2) Bugs/errors, 3) Security issues, 4) Best practices. Give specific comments with suggested fixes."
BODY=$(jq -n \ BODY=$(jq -n \
--arg model "qwen3-coder-next:latest" \ --arg model "qwen3-coder-next:latest" \
@ -62,7 +67,7 @@ jobs:
'{model: $model, messages: [{role: "user", content: $content}], stream: false}') '{model: $model, messages: [{role: "user", content: $content}], stream: false}')
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] PR #${PR_NUMBER} - Calling Ollama API (${#BODY} bytes)..." echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] PR #${PR_NUMBER} - Calling Ollama API (${#BODY} bytes)..."
HTTP_CODE=$(curl -s --max-time 120 --connect-timeout 30 \ HTTP_CODE=$(curl -s --max-time 120 --connect-timeout 30 \
--retry 3 --retry-delay 5 --retry-all-errors \ --retry 3 --retry-delay 5 --retry-connrefused --retry-max-time 120 \
-o /tmp/ollama_response.json -w "%{http_code}" \ -o /tmp/ollama_response.json -w "%{http_code}" \
-X POST "$OLLAMA_URL/chat/completions" \ -X POST "$OLLAMA_URL/chat/completions" \
-H "Authorization: Bearer $OLLAMA_API_KEY" \ -H "Authorization: Bearer $OLLAMA_API_KEY" \
@ -70,21 +75,23 @@ jobs:
-d "$BODY") -d "$BODY")
echo "HTTP status: $HTTP_CODE" echo "HTTP status: $HTTP_CODE"
echo "Response file size: $(wc -c < /tmp/ollama_response.json) bytes" echo "Response file size: $(wc -c < /tmp/ollama_response.json) bytes"
if [ "$HTTP_CODE" != "200" ]; then
echo "ERROR: Ollama returned HTTP $HTTP_CODE"
cat /tmp/ollama_response.json
exit 1
fi
if ! jq empty /tmp/ollama_response.json 2>/dev/null; then if ! jq empty /tmp/ollama_response.json 2>/dev/null; then
echo "ERROR: Invalid JSON response from Ollama" echo "ERROR: Invalid JSON response from Ollama"
cat /tmp/ollama_response.json cat /tmp/ollama_response.json
exit 1 exit 1
fi fi
jq . /tmp/ollama_response.json jq . /tmp/ollama_response.json
if [ "$HTTP_CODE" != "200" ]; then
echo "ERROR: Ollama returned HTTP $HTTP_CODE"
exit 1
fi
REVIEW=$(jq -r '.choices[0].message.content // empty' /tmp/ollama_response.json) REVIEW=$(jq -r '.choices[0].message.content // empty' /tmp/ollama_response.json)
if [ -z "$REVIEW" ]; then if [ -z "$REVIEW" ]; then
echo "ERROR: No content in Ollama response" echo "ERROR: No content in Ollama response"
exit 1 exit 1
fi fi
echo "Review length: ${#REVIEW} chars"
echo "$REVIEW" > /tmp/pr_review.txt echo "$REVIEW" > /tmp/pr_review.txt
- name: Post review comment - name: Post review comment
@ -93,6 +100,8 @@ jobs:
env: env:
TF_TOKEN: ${{ secrets.TFT_GITEA_TOKEN }} TF_TOKEN: ${{ secrets.TFT_GITEA_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }} PR_NUMBER: ${{ github.event.pull_request.number }}
SERVER_URL: ${{ github.server_url }}
REPOSITORY: ${{ github.repository }}
run: | run: |
set -euo pipefail set -euo pipefail
if [ -z "${TF_TOKEN:-}" ]; then if [ -z "${TF_TOKEN:-}" ]; then
@ -110,7 +119,7 @@ jobs:
fi fi
HTTP_CODE=$(curl -s --max-time 30 --connect-timeout 10 \ HTTP_CODE=$(curl -s --max-time 30 --connect-timeout 10 \
-o /tmp/review_post_response.json -w "%{http_code}" \ -o /tmp/review_post_response.json -w "%{http_code}" \
-X POST "https://gogs.tftsr.com/api/v1/repos/sarman/tftsr-devops_investigation/pulls/$PR_NUMBER/reviews" \ -X POST "${SERVER_URL}/api/v1/repos/${REPOSITORY}/pulls/${PR_NUMBER}/reviews" \
-H "Authorization: token $TF_TOKEN" \ -H "Authorization: token $TF_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$BODY") -d "$BODY")