From 845abc380e0841f142ca0b1a652e9b521cb9dcb8 Mon Sep 17 00:00:00 2001 From: Sanaei Date: Thu, 20 Aug 2026 15:59:51 +0200 Subject: [PATCH] fix(ci): make the review bot post its findings and acknowledge mentions Three separate ways the bot went silent after the move to the official code-review skill: - The skill skips a PR it has already commented on without comparing the reviewed head to the current one, so #6272 got no review of the commits pushed after the first pass. A prior review now only justifies a skip when its "Reviewed head:" SHA matches the current head, and never when the run came from an explicit "@claude review". - The review agent launched its subagents in the background and ended its turn to wait for them. A headless run terminates on end_turn, so the findings were discarded and the job still reported success. The prompt now requires foreground subagents, and a new step fails the job when a run posts nothing for the current head, instead of passing green. - A custom prompt puts claude-code-action in agent mode, which never adds the eyes reaction, so a mention gave no sign it had been picked up. --- .github/workflows/claude-bot.yml | 44 +++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-bot.yml b/.github/workflows/claude-bot.yml index b360159fb..53faf687d 100644 --- a/.github/workflows/claude-bot.yml +++ b/.github/workflows/claude-bot.yml @@ -478,6 +478,19 @@ jobs: issues: read id-token: write steps: + - name: Record when this run started + id: started + run: echo "at=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" + # A custom prompt puts the action in agent mode, which never reacts on its + # own, so the requester gets no sign the run started. + - name: Acknowledge the request + if: github.event_name == 'issue_comment' + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + COMMENT_ID: ${{ github.event.comment.id }} + run: gh api "repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" -f content=eyes - uses: actions/checkout@v7 with: persist-credentials: false @@ -494,7 +507,7 @@ jobs: --effort xhigh --max-turns 100 --allowedTools "mcp__github_inline_comment__create_inline_comment" - --append-system-prompt "Before reviewing, read REVIEW.md at the repository root and follow it: it defines what counts as a blocking finding in this repository, what not to report, and the repo-specific checks." + --append-system-prompt "Before reviewing, read REVIEW.md at the repository root and follow it: it defines what counts as a blocking finding in this repository, what not to report, and the repo-specific checks. Two overrides apply here. First, the skip gate for already-reviewed PRs: an existing Claude review comment justifies skipping ONLY when its 'Reviewed head:' SHA equals the PR's current head SHA; when the head has moved on, or this run was triggered by an explicit '@claude review' comment, run the full review, focusing on the commits since the previously reviewed head. Second, this is a headless run that terminates the moment you end your turn: launch every subagent with run_in_background set to false and wait for its result inside the same turn - never end your turn while a subagent is still running, and never end it before the review comment is posted. A run that ends without posting the review has failed." - name: Upload the run transcript if: always() env: @@ -505,6 +518,26 @@ jobs: path: ${{ runner.temp }}/claude-execution-output.json if-no-files-found: ignore retention-days: 7 + - name: Fail if the review posted nothing + if: ${{ !cancelled() }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + PR: ${{ github.event.pull_request.number || github.event.issue.number }} + STARTED_AT: ${{ steps.started.outputs.at }} + run: | + set -euo pipefail + head=$(gh api "repos/${REPO}/pulls/${PR}" --jq '.head.sha') + # updated_at, not created_at: the skill may update its existing sticky comment. + # A pre-existing comment naming the current head SHA means a legitimate skip. + posted=$(gh api "repos/${REPO}/issues/${PR}/comments" --paginate \ + --jq "[.[] | select(.user.login == \"github-actions[bot]\") | select((.updated_at >= \"${STARTED_AT}\") or (.body | contains(\"${head}\")))] | length") + inline=$(gh api "repos/${REPO}/pulls/${PR}/comments" --paginate \ + --jq "[.[] | select(.user.login == \"github-actions[bot]\") | select(.updated_at >= \"${STARTED_AT}\")] | length") + if [ "$posted" = "0" ] && [ "$inline" = "0" ]; then + echo "::error::The review run ended without posting a review of ${head} on #${PR}. Read the uploaded transcript before re-running." + exit 1 + fi mention: if: >- @@ -525,6 +558,15 @@ jobs: pull-requests: write id-token: write steps: + # A custom prompt puts the action in agent mode, which never reacts on its + # own, so the requester gets no sign the run started. + - name: Acknowledge the mention + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + COMMENT_ID: ${{ github.event.comment.id }} + run: gh api "repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" -f content=eyes - uses: actions/checkout@v7 with: fetch-depth: 0