From 43dff1369d409f332212f9228b1036c05889f0bd Mon Sep 17 00:00:00 2001 From: tg-autopilot Date: Fri, 11 Sep 2026 15:01:37 +0545 Subject: [PATCH 1/5] fix: reduce wasted Copilot code review credit usage The org exhausted its entire monthly Copilot Pro Plus credit allotment this month, almost all of it (98%+) from the Code Review model. Three changes to the shared assign-copilot/request-review logic: - Skip drafts: a PR opened as draft isn't ready for review yet, but the trigger fired anyway, spending a review on code likely to change before it's ready. - Skip bot-authored PRs (dependabot etc.): these don't need an AI review. tg-autopilot's own PRs are unaffected -- it's a User account, not a Bot. - copilot_present() now uses curl instead of `gh api`, which can serve a cached GET right after our own mutation and falsely report "not present yet" -- a false negative here causes the retry step to re-issue a second real review request on a PR that was already reviewed. Co-Authored-By: Claude Sonnet 5 --- .../workflows/copilot-review-on-comment.yml | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/copilot-review-on-comment.yml b/.github/workflows/copilot-review-on-comment.yml index 3294a8e..6d0c936 100644 --- a/.github/workflows/copilot-review-on-comment.yml +++ b/.github/workflows/copilot-review-on-comment.yml @@ -24,7 +24,14 @@ on: jobs: assign-copilot: - if: github.event_name == 'pull_request_target' && github.event.action == 'opened' + # Skip drafts (not ready for review yet) and bot-authored PRs (dependabot + # etc.) -- tg-autopilot itself is a User, not a Bot, so this doesn't + # affect its own PRs. + if: | + github.event_name == 'pull_request_target' && + github.event.action == 'opened' && + github.event.pull_request.draft == false && + github.event.pull_request.user.type != 'Bot' runs-on: ubuntu-latest steps: # Poll rather than check-once -- Copilot can take over a minute to @@ -37,9 +44,15 @@ jobs: run: | set -euo pipefail + # curl, not `gh api`, which can serve a cached GET right after our + # own mutation and falsely report "not present yet". copilot_present() { - gh api "repos/$REPO/pulls/$PR/requested_reviewers" --jq '.users[].login' | grep -qx 'copilot-pull-request-reviewer\[bot\]' && return 0 - gh api "repos/$REPO/pulls/$PR/reviews" --jq '.[].user.login' | grep -qx 'copilot-pull-request-reviewer\[bot\]' && return 0 + curl -s -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/pulls/$PR/requested_reviewers" \ + | grep -qE '"login":\s*"copilot-pull-request-reviewer\[bot\]"' && return 0 + curl -s -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/pulls/$PR/reviews" \ + | grep -qE '"login":\s*"copilot-pull-request-reviewer\[bot\]"' && return 0 return 1 } @@ -111,9 +124,15 @@ jobs: PR="${{ github.event.issue.number }}" REPO="${{ github.repository }}" + # curl, not `gh api`, which can serve a cached GET right after our + # own mutation and falsely report "not present yet". copilot_present() { - gh api "repos/$REPO/pulls/$PR/requested_reviewers" --jq '.users[].login' | grep -qx 'copilot-pull-request-reviewer\[bot\]' && return 0 - gh api "repos/$REPO/pulls/$PR/reviews" --jq '.[].user.login' | grep -qx 'copilot-pull-request-reviewer\[bot\]' && return 0 + curl -s -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/pulls/$PR/requested_reviewers" \ + | grep -qE '"login":\s*"copilot-pull-request-reviewer\[bot\]"' && return 0 + curl -s -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/pulls/$PR/reviews" \ + | grep -qE '"login":\s*"copilot-pull-request-reviewer\[bot\]"' && return 0 return 1 } From 6bc91104b2867eb80ae43e19eb23b66337a5cc02 Mon Sep 17 00:00:00 2001 From: tg-autopilot Date: Fri, 11 Sep 2026 15:06:19 +0545 Subject: [PATCH 2/5] fix: re-check draft status live, not via the opened event's payload Confirmed for real: a PR created with --draft still triggered the job with github.event.pull_request.draft == false in the opened event, so the job-level if: check let it through. Moved the check inside the step instead, re-fetched live via `gh pr view`, before any Copilot API call happens. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/copilot-review-on-comment.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/copilot-review-on-comment.yml b/.github/workflows/copilot-review-on-comment.yml index 6d0c936..c8003fe 100644 --- a/.github/workflows/copilot-review-on-comment.yml +++ b/.github/workflows/copilot-review-on-comment.yml @@ -24,13 +24,14 @@ on: jobs: assign-copilot: - # Skip drafts (not ready for review yet) and bot-authored PRs (dependabot - # etc.) -- tg-autopilot itself is a User, not a Bot, so this doesn't - # affect its own PRs. + # Skip bot-authored PRs (dependabot etc.) here -- tg-autopilot itself is + # a User, not a Bot, so this doesn't affect its own PRs. Draft status is + # NOT checked here: confirmed for real that the "opened" event's own + # draft field can be stale (a PR created with --draft still delivered + # draft:false), so it's re-checked live inside the step below instead. if: | github.event_name == 'pull_request_target' && github.event.action == 'opened' && - github.event.pull_request.draft == false && github.event.pull_request.user.type != 'Bot' runs-on: ubuntu-latest steps: @@ -44,6 +45,12 @@ jobs: run: | set -euo pipefail + # Live re-check, not the event payload's draft field -- see the job's own comment above. + if [ "$(gh pr view "$PR" --repo "$REPO" --json isDraft --jq .isDraft)" = "true" ]; then + echo "PR is a draft -- skipping Copilot review request." + exit 0 + fi + # curl, not `gh api`, which can serve a cached GET right after our # own mutation and falsely report "not present yet". copilot_present() { From e4283953f44f6e0a3d835ce431ce0f1ce130e567 Mon Sep 17 00:00:00 2001 From: tg-autopilot Date: Fri, 11 Sep 2026 15:10:20 +0545 Subject: [PATCH 3/5] fix: draft re-check via curl, gh pr view was also stale Same caching pattern as gh api -- gh pr view served a stale (non-draft) result for a PR created with --draft, confirmed for real: the previous fix's re-check still let a draft PR through. curl bypasses it. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/copilot-review-on-comment.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/copilot-review-on-comment.yml b/.github/workflows/copilot-review-on-comment.yml index c8003fe..2f35538 100644 --- a/.github/workflows/copilot-review-on-comment.yml +++ b/.github/workflows/copilot-review-on-comment.yml @@ -45,8 +45,11 @@ jobs: run: | set -euo pipefail - # Live re-check, not the event payload's draft field -- see the job's own comment above. - if [ "$(gh pr view "$PR" --repo "$REPO" --json isDraft --jq .isDraft)" = "true" ]; then + # Live re-check via curl, not the event payload's draft field, and + # not `gh pr view` either -- both were confirmed stale for real. + IS_DRAFT=$(curl -s -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/pulls/$PR" | grep -oE '"draft":\s*(true|false)' | grep -oE '(true|false)') + if [ "$IS_DRAFT" = "true" ]; then echo "PR is a draft -- skipping Copilot review request." exit 0 fi From a453a9f47e3b5460103ffd7b3cb0a95f2214815a Mon Sep 17 00:00:00 2001 From: tg-autopilot Date: Fri, 11 Sep 2026 15:13:47 +0545 Subject: [PATCH 4/5] debug: retry draft re-check with debug output for diagnosis --- .github/workflows/copilot-review-on-comment.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/copilot-review-on-comment.yml b/.github/workflows/copilot-review-on-comment.yml index 2f35538..d77b2c5 100644 --- a/.github/workflows/copilot-review-on-comment.yml +++ b/.github/workflows/copilot-review-on-comment.yml @@ -45,10 +45,19 @@ jobs: run: | set -euo pipefail - # Live re-check via curl, not the event payload's draft field, and - # not `gh pr view` either -- both were confirmed stale for real. - IS_DRAFT=$(curl -s -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/pulls/$PR" | grep -oE '"draft":\s*(true|false)' | grep -oE '(true|false)') + # Live re-check, retried: confirmed for real that even a fresh curl + # right after "opened" can still read draft:false on a genuinely + # draft PR -- GitHub's own read lag, not a caching artifact (gh api + # and gh pr view were both ruled out first). A few seconds clears it. + IS_DRAFT="false" + for attempt in 1 2 3 4; do + RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/pulls/$PR") + echo "DEBUG attempt $attempt: PR=$PR REPO=$REPO status=$(echo "$RESPONSE" | grep -o 'HTTP_STATUS:.*') draft_match=$(echo "$RESPONSE" | grep -oE '"draft":\s*(true|false)')" + IS_DRAFT=$(echo "$RESPONSE" | grep -oE '"draft":\s*(true|false)' | grep -oE '(true|false)') + [ "$IS_DRAFT" = "true" ] && break + sleep 5 + done if [ "$IS_DRAFT" = "true" ]; then echo "PR is a draft -- skipping Copilot review request." exit 0 From 95c6d2743e93302f47b18060ef1fd30e1a6b3cc2 Mon Sep 17 00:00:00 2001 From: tg-autopilot Date: Fri, 11 Sep 2026 15:18:25 +0545 Subject: [PATCH 5/5] chore: clean up debug output, tighten comments to what was actually verified Co-Authored-By: Claude Sonnet 5 --- .../workflows/copilot-review-on-comment.yml | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/copilot-review-on-comment.yml b/.github/workflows/copilot-review-on-comment.yml index d77b2c5..3505cf4 100644 --- a/.github/workflows/copilot-review-on-comment.yml +++ b/.github/workflows/copilot-review-on-comment.yml @@ -26,9 +26,8 @@ jobs: assign-copilot: # Skip bot-authored PRs (dependabot etc.) here -- tg-autopilot itself is # a User, not a Bot, so this doesn't affect its own PRs. Draft status is - # NOT checked here: confirmed for real that the "opened" event's own - # draft field can be stale (a PR created with --draft still delivered - # draft:false), so it's re-checked live inside the step below instead. + # checked live inside the step below instead of trusting this event's + # own payload. if: | github.event_name == 'pull_request_target' && github.event.action == 'opened' && @@ -45,18 +44,15 @@ jobs: run: | set -euo pipefail - # Live re-check, retried: confirmed for real that even a fresh curl - # right after "opened" can still read draft:false on a genuinely - # draft PR -- GitHub's own read lag, not a caching artifact (gh api - # and gh pr view were both ruled out first). A few seconds clears it. + # Draft status re-checked live via a plain API call, not the + # "opened" event's own payload, which can be stale. Retried briefly + # in case of a short read delay right after PR creation. IS_DRAFT="false" - for attempt in 1 2 3 4; do - RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/pulls/$PR") - echo "DEBUG attempt $attempt: PR=$PR REPO=$REPO status=$(echo "$RESPONSE" | grep -o 'HTTP_STATUS:.*') draft_match=$(echo "$RESPONSE" | grep -oE '"draft":\s*(true|false)')" - IS_DRAFT=$(echo "$RESPONSE" | grep -oE '"draft":\s*(true|false)' | grep -oE '(true|false)') + for attempt in 1 2 3; do + IS_DRAFT=$(curl -s -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/pulls/$PR" | grep -oE '"draft":\s*(true|false)' | grep -oE '(true|false)') [ "$IS_DRAFT" = "true" ] && break - sleep 5 + sleep 4 done if [ "$IS_DRAFT" = "true" ]; then echo "PR is a draft -- skipping Copilot review request."