Skip to content

Activation comment / reaction not posted for pull_request_review triggers — buildReactionLikeCondition allowlist is incomplete #30336

@mason-tim

Description

@mason-tim

Summary

When a workflow's on: block includes pull_request_review (e.g. pull_request_review: types: [submitted]) and a run is triggered by that event, the gh-aw runtime does not post the auto-activation comment (the one configured via safe-outputs.messages.run-started) and does not apply the configured on.reaction emoji to the triggering item. The same auto-comment fires correctly for issues, issue_comment, pull_request_review_comment, discussion, discussion_comment, and same-repo pull_request triggers; it's silently skipped for pull_request_review.

The downstream effects are:

  1. The run-started comment never appears — users have no visible signal that the workflow has picked up the work, so they don't know whether anything is happening until the agent posts something itself or pushes a commit.
  2. Because there's no comment_id from the activation step, the corresponding run-success / run-failure update steps also have nothing to update for this event path.
  3. The eyes-reaction step is gated by the same condition, so the "👀" reaction also never appears.

This breaks the user-visible contract of the safe-outputs.messages and on.reaction features for any workflow that listens on pull_request_review.

Analysis

Root Cause

pkg/workflow/expression_builder.go, buildReactionLikeCondition (around lines 90–123) builds the if: expression used to gate both the activation/status comment and the eyes reaction. It enumerates a fixed list of event types:

func buildReactionLikeCondition(includeIssues bool, includePullRequests bool, includeDiscussions bool) ConditionNode {
    var terms []ConditionNode

    if includeIssues {
        terms = append(terms, BuildEventTypeEquals("issues"))
        terms = append(terms, BuildEventTypeEquals("issue_comment"))
    }
    if includePullRequests {
        terms = append(terms, BuildEventTypeEquals("pull_request_review_comment"))
    }
    if includeDiscussions {
        terms = append(terms, BuildEventTypeEquals("discussion"))
        terms = append(terms, BuildEventTypeEquals("discussion_comment"))
    }

    if includePullRequests {
        pullRequestCondition := &AndNode{
            Left:  BuildEventTypeEquals("pull_request"),
            Right: BuildNotFromFork(),
        }
        terms = append(terms, pullRequestCondition)
    }
    …
}

The function is shared by BuildReactionCondition and BuildStatusCommentCondition (same file). Neither path adds pull_request_review (nor pull_request_target, nor pull_request_review_thread) to the allowlist when includePullRequests is true.

The compiled lock files end up with an if: like:

if: github.event_name == 'issues'
    || github.event_name == 'issue_comment'
    || github.event_name == 'pull_request_review_comment'
    || github.event_name == 'discussion'
    || github.event_name == 'discussion_comment'
    || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id

…on both the "Add eyes reaction for immediate feedback" step and the "Add comment with workflow run link" step. When the actual trigger is pull_request_review, the condition evaluates to false and both steps no-op. The agent then runs (correctly), but the user-visible "I'm on it" signal is missing.

Reproducer

Minimal workflow that demonstrates the asymmetry — three triggers, all configured to react/comment, only one is silent:

on:
  slash_command:
    name: my-bot
    events: [pull_request_comment, pull_request_review_comment]
  pull_request:
    types: [labeled]
    names: [my-bot]
  pull_request_review:
    types: [submitted]

reaction: eyes

safe-outputs:
  messages:
    run-started: "👋 {workflow_name} is on it — {run_url}"
  add-comment: {}

Trigger each of the three paths in turn on a same-repo PR and observe the safe-outputs job in the run UI:

Trigger path 👀 reaction run-started comment
/my-bot slash command
my-bot label applied
Reviewer submits a review (Approve/Comment/Request changes)

The compiled lock file shows the gating step's if: evaluating false on the third path because github.event_name == 'pull_request_review' does not appear in the disjunction.

Why this matters

pull_request_review is the natural trigger for any "respond to a reviewer" workflow — code-quality bots, auto-fixers, comment triagers, etc. Asking workflow authors to re-implement the started/eyes UX themselves in the agent prompt duplicates logic that the framework already owns and makes the user experience inconsistent across event paths within the same workflow.

Proposed Fix

In buildReactionLikeCondition, add pull_request_review to the includePullRequests branch, behind the same not-from-fork guard used for pull_request:

if includePullRequests {
    terms = append(terms, BuildEventTypeEquals("pull_request_review_comment"))

    pullRequestReviewCondition := &AndNode{
        Left:  BuildEventTypeEquals("pull_request_review"),
        Right: BuildNotFromFork(),
    }
    terms = append(terms, pullRequestReviewCondition)
}

…

if includePullRequests {
    pullRequestCondition := &AndNode{
        Left:  BuildEventTypeEquals("pull_request"),
        Right: BuildNotFromFork(),
    }
    terms = append(terms, pullRequestCondition)
}

BuildNotFromFork already references github.event.pull_request.head.repo.id, which is populated for pull_request_review events too, so the same fork-guard expression works without modification.

Tests to add

pkg/workflow/expression_builder_test.go (or wherever the reaction/status-comment condition coverage lives) should gain cases asserting that:

  1. A workflow with on.pull_request_review and safe-outputs.messages.run-started configured produces an if: that evaluates true for event_name == 'pull_request_review' on a same-repo PR.
  2. The same condition evaluates false on a fork PR (consistent with the pull_request behaviour).
  3. The activation comment step's if: and the Add eyes reaction step's if: both include the new term, since both go through buildReactionLikeCondition.

Out of Scope (Possible Follow-ups)

  • pull_request_target and pull_request_review_thread are also missing from the allowlist. They have different security implications (especially pull_request_target, which runs with write permissions on PRs from forks) and may warrant their own discussion before being added.
  • Documentation in docs/safe-outputs.md (or wherever messages.run-started is described) could usefully list which trigger event types actually post the activation comment, since this is currently surprising.

Workaround (for consumers blocked today)

Until the framework fix lands, workflow authors can post their own activation comment from inside the agent prompt, gated on ${{ github.event_name == 'pull_request_review' }}. This requires:

  1. Adding an add-comment safe-output to the workflow.
  2. A "Step 0" instruction in the prompt telling the agent to fire the comment only when triggered by pull_request_review, so the framework's own auto-comment isn't duplicated on the other trigger paths.

It's a viable stop-gap but the UX is no longer consistent with how the framework handles the issue/discussion/pull_request paths.

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions