From 71fb6868851ed2809ee4ff600fc78b7da6faedbb Mon Sep 17 00:00:00 2001 From: Vlad Mocanu Date: Thu, 26 Feb 2026 10:05:20 +0200 Subject: [PATCH] fix: gate Forgejo review/comment triggers on @claude mention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, all pull_request_comment events on Forgejo triggered the full Claude pipeline unconditionally — both in the workflow if-condition and in trigger.ts. This wasted CI resources and API credits on reviews without @claude mentions (e.g. plain "LGTM" reviews). Workflow: add @claude check via contains() on comment.body and review.body. Trigger validation: replace blind auto-trigger with payload-aware check that inspects available body fields, falling back only when no body exists. Co-Authored-By: Claude Opus 4.6 --- .forgejo/workflows/claude.yml | 3 ++- src/github/validation/trigger.ts | 39 ++++++++++++++++++-------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.forgejo/workflows/claude.yml b/.forgejo/workflows/claude.yml index ea81a3d..1f61505 100644 --- a/.forgejo/workflows/claude.yml +++ b/.forgejo/workflows/claude.yml @@ -24,7 +24,8 @@ jobs: # despite action being 'labeled'). Let all through; trigger.ts validates. if: >- (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' || github.event_name == 'pull_request_comment') || + (github.event_name == 'pull_request_review_comment' || github.event_name == 'pull_request_comment') && + (contains(github.event.comment.body, '@claude') || contains(github.event.review.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'issues') runs-on: grunt diff --git a/src/github/validation/trigger.ts b/src/github/validation/trigger.ts index 809018e..55e5c66 100644 --- a/src/github/validation/trigger.ts +++ b/src/github/validation/trigger.ts @@ -123,32 +123,37 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean { isIssueCommentEvent(context) || isPullRequestReviewCommentEvent(context) ) { - // Forgejo's pull_request_review_comment payload does NOT include a - // top-level `comment` object (it's undefined). On Forgejo, the - // workflow `if:` already gates on event name alone, so always trigger - // here — the actual comment data is fetched via REST in the data - // fetcher, and non-matching comments won't produce useful output. - if ( - isPullRequestReviewCommentEvent(context) && - detectPlatform() === "forgejo" - ) { - console.log( - "Forgejo pull_request_review_comment: triggering (payload lacks comment body)", - ); - return true; - } - + // Forgejo sends both review comments and full reviews as + // `pull_request_comment`, which normalizes to `pull_request_review_comment`. + // The payload may have `comment.body`, `review.body`, both, or neither. + // Try all available body fields before giving up. + const payload = context.payload as Record; const commentBody = isIssueCommentEvent(context) ? context.payload.comment.body - : context.payload.comment.body; + : (payload.comment?.body ?? payload.review?.body ?? ""); + // Check for exact match with word boundaries or punctuation const regex = new RegExp( `(^|\\s)${escapeRegExp(triggerPhrase)}([\\s.,!?;:]|$)`, ); - if (regex.test(commentBody)) { + if (commentBody && regex.test(commentBody)) { console.log(`Comment contains exact trigger phrase '${triggerPhrase}'`); return true; } + + // On Forgejo, if no body was found in the payload at all, still trigger — + // the actual comment data will be fetched via REST in the data fetcher. + // This handles edge cases where Forgejo strips body from the webhook payload. + if ( + isPullRequestReviewCommentEvent(context) && + detectPlatform() === "forgejo" && + !commentBody + ) { + console.log( + "Forgejo pull_request_review_comment: no body in payload, triggering (data fetched via REST)", + ); + return true; + } } console.log(`No trigger was met for ${triggerPhrase}`);