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:
- 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.
- 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.
- 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:
- 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.
- The same condition evaluates false on a fork PR (consistent with the
pull_request behaviour).
- 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:
- Adding an
add-comment safe-output to the workflow.
- 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.
Summary
When a workflow's
on:block includespull_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 viasafe-outputs.messages.run-started) and does not apply the configuredon.reactionemoji to the triggering item. The same auto-comment fires correctly forissues,issue_comment,pull_request_review_comment,discussion,discussion_comment, and same-repopull_requesttriggers; it's silently skipped forpull_request_review.The downstream effects are:
run-startedcomment 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.comment_idfrom the activation step, the correspondingrun-success/run-failureupdate steps also have nothing to update for this event path.This breaks the user-visible contract of the
safe-outputs.messagesandon.reactionfeatures for any workflow that listens onpull_request_review.Analysis
Root Cause
pkg/workflow/expression_builder.go,buildReactionLikeCondition(around lines 90–123) builds theif:expression used to gate both the activation/status comment and the eyes reaction. It enumerates a fixed list of event types:The function is shared by
BuildReactionConditionandBuildStatusCommentCondition(same file). Neither path addspull_request_review(norpull_request_target, norpull_request_review_thread) to the allowlist whenincludePullRequestsis true.The compiled lock files end up with an
if:like:…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:
Trigger each of the three paths in turn on a same-repo PR and observe the
safe-outputsjob in the run UI:run-startedcomment/my-botslash commandmy-botlabel appliedThe compiled lock file shows the gating step's
if:evaluating false on the third path becausegithub.event_name == 'pull_request_review'does not appear in the disjunction.Why this matters
pull_request_reviewis 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, addpull_request_reviewto theincludePullRequestsbranch, behind the same not-from-fork guard used forpull_request:BuildNotFromForkalready referencesgithub.event.pull_request.head.repo.id, which is populated forpull_request_reviewevents 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:on.pull_request_reviewandsafe-outputs.messages.run-startedconfigured produces anif:that evaluates true forevent_name == 'pull_request_review'on a same-repo PR.pull_requestbehaviour).if:and theAdd eyes reactionstep'sif:both include the new term, since both go throughbuildReactionLikeCondition.Out of Scope (Possible Follow-ups)
pull_request_targetandpull_request_review_threadare also missing from the allowlist. They have different security implications (especiallypull_request_target, which runs with write permissions on PRs from forks) and may warrant their own discussion before being added.docs/safe-outputs.md(or wherevermessages.run-startedis 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:add-commentsafe-output to the workflow.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_requestpaths.