Skip to content

Commit 9e23afd

Browse files
bokelleyclaude
andauthored
ci(triage): fire on PR comments + send is_pr/pr context to routine (#286)
Port from adcontextprotocol/adcp#3316. Drops pull_request == null filter, adds is_pr/pr context to routine payload, widens self-loop guard to skip "Fixed by Claude Code" replies. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5312f05 commit 9e23afd

1 file changed

Lines changed: 47 additions & 12 deletions

File tree

.github/workflows/claude-issue-triage.yml

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
name: Claude Issue Triage
22

33
# Fires the Claude Code triage routine's /fire endpoint when a new issue
4-
# opens OR when a repo member invokes `/triage` via comment (dispatched by
4+
# opens, when a comment lands on an issue OR PR, or when a repo member
5+
# invokes `/triage` via comment (dispatched by
56
# `.github/workflows/slash-command-dispatch.yml`).
67
#
7-
# The issue body is fetched fresh and passed as *data* (fenced, size-capped) —
8-
# the routine's prompt treats anything inside the fence as untrusted content.
8+
# Both issue comments and PR comments are routed to the same routine; the
9+
# payload's `is_pr` flag and `pr` block tell the routine which context it's
10+
# in so it can pick the right response (issue triage vs PR-feedback fix).
11+
#
12+
# The issue/PR body is fetched fresh and passed as *data* (fenced, size-
13+
# capped) — the routine's prompt treats anything inside the fence as
14+
# untrusted content.
915
#
1016
# Required repo secrets:
1117
# CLAUDE_ROUTINE_TRIAGE_URL — full /fire URL including routine ID
@@ -35,14 +41,14 @@ jobs:
3541
name: Fire triage routine
3642
runs-on: ubuntu-latest
3743
timeout-minutes: 3
38-
# Skip bot-opened issues. Manual path already gated by slash-dispatch
39-
# permission check.
40-
# Three trigger paths, each gated:
44+
# Three trigger paths, each with its own gate:
4145
# - issues.opened/reopened → skip bot-opened issues
42-
# - issue_comment.created → skip bots, skip routine self-loops
43-
# ("Triaged by Claude Code" footer),
44-
# skip /triage (handled by repo_dispatch),
45-
# skip PR conversations (auto-fix's job)
46+
# - issue_comment.created → skip bots, skip self-loop (routine's
47+
# own comments contain "Triaged by
48+
# Claude Code"), skip /triage (handled
49+
# by slash-command-dispatch path).
50+
# Both issue and PR comments fire — the
51+
# routine receives `is_pr` to branch on.
4652
# - repository_dispatch → always allow (slash-dispatch already
4753
# gated by member-association check)
4854
if: >-
@@ -59,7 +65,7 @@ jobs:
5965
github.event.sender.type != 'Bot' &&
6066
!startsWith(github.event.comment.body, '/triage') &&
6167
!contains(github.event.comment.body, 'Triaged by Claude Code') &&
62-
github.event.issue.pull_request == null
68+
!contains(github.event.comment.body, 'Fixed by Claude Code')
6369
) ||
6470
github.event_name == 'repository_dispatch'
6571
defaults:
@@ -122,9 +128,28 @@ jobs:
122128
assoc=$(echo "$issue" | jq -r '.author_association // "NONE"')
123129
labels=$(echo "$issue" | jq -c '[.labels[].name]')
124130
html_url=$(echo "$issue" | jq -r '.html_url')
131+
# GitHub's issues API populates `pull_request` only when the issue
132+
# is actually a PR. When present, fetch PR-specific fields so the
133+
# routine can branch on context (head/base ref, draft status, etc.).
134+
is_pr=$(echo "$issue" | jq -r 'if .pull_request then "true" else "false" end')
125135
126136
body_safe=$(printf '%s' "$body" | tr -d '\000' | head -c 8192)
127137
138+
pr_block=""
139+
if [ "$is_pr" = "true" ]; then
140+
pr=$(gh api "repos/$REPO/pulls/$ISSUE_NUMBER")
141+
pr_head=$(echo "$pr" | jq -r '.head.ref')
142+
pr_base=$(echo "$pr" | jq -r '.base.ref')
143+
pr_draft=$(echo "$pr" | jq -r '.draft')
144+
pr_state=$(echo "$pr" | jq -r '.state')
145+
pr_block=$(jq -n \
146+
--arg head "$pr_head" \
147+
--arg base "$pr_base" \
148+
--arg draft "$pr_draft" \
149+
--arg state "$pr_state" \
150+
'{head_ref: $head, base_ref: $base, draft: ($draft == "true"), state: $state}')
151+
fi
152+
128153
# For comment-driven runs, fetch the specific comment so the routine
129154
# can act on it. The full issue body is also included so the routine
130155
# has the original context, not just the new prose.
@@ -159,13 +184,23 @@ jobs:
159184
--arg comment_body "$comment_body_safe" \
160185
--arg comment_author "$comment_author" \
161186
--arg comment_assoc "$comment_assoc" \
187+
--arg is_pr "$is_pr" \
188+
--arg pr_block "$pr_block" \
162189
'{text: (
163190
"Event: " + $kind + "." + $action + "\n" +
164191
"Repo: " + $repo + "\n" +
165-
"Issue: #" + $num + " \"" + $title + "\"\n" +
192+
(if $is_pr == "true" then "PR" else "Issue" end) +
193+
": #" + $num + " \"" + $title + "\"\n" +
166194
"URL: " + $url + "\n" +
167195
"Author: @" + $author + " (association: " + $assoc + ")\n" +
168196
"Labels: " + ($labels | join(", ")) + "\n" +
197+
(if $is_pr == "true" then
198+
"is_pr: true\n" +
199+
"pr: " + $pr_block + "\n" +
200+
"MODE: PR-feedback. Treat new comment as actionable feedback on the PR diff. If the comment requests a fix, apply it as a follow-up commit on the PR's head branch (do not open a new PR). If the comment asks a question, answer it as a reply comment. If the comment is conversational with no action implied, post a short acknowledgement and stop.\n"
201+
else
202+
"is_pr: false\n"
203+
end) +
169204
(if $nudge == "" then "" else $nudge + "\n" end) +
170205
(if $comment_body == "" then "" else
171206
"\nNew comment by @" + $comment_author +

0 commit comments

Comments
 (0)