Skip to content

Commit 2cbf346

Browse files
jbingham17claude
andcommitted
Thread Slack notifications by PR, post parent message on PR open
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 931347b commit 2cbf346

File tree

2 files changed

+123
-54
lines changed

2 files changed

+123
-54
lines changed
Lines changed: 121 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
name: PR Comment Slack Notification
22

33
on:
4+
pull_request:
5+
types: [opened, reopened]
46
issue_comment:
57
types: [created]
68
pull_request_review_comment:
@@ -10,6 +12,7 @@ jobs:
1012
notify-slack:
1113
# For issue_comment, only run if the comment is on a pull request
1214
if: >
15+
github.event_name == 'pull_request' ||
1316
github.event_name == 'pull_request_review_comment' ||
1417
(github.event_name == 'issue_comment' && github.event.issue.pull_request)
1518
runs-on: ubuntu-latest
@@ -24,14 +27,27 @@ jobs:
2427
COMMENTER: ${{ github.event.comment.user.login }}
2528
COMMENTER_AVATAR: ${{ github.event.comment.user.avatar_url }}
2629
REPO: ${{ github.repository }}
30+
# pull_request event
31+
PR_URL_PR: ${{ github.event.pull_request.html_url }}
32+
PR_TITLE_PR: ${{ github.event.pull_request.title }}
33+
PR_NUMBER_PR: ${{ github.event.pull_request.number }}
34+
PR_AUTHOR_PR: ${{ github.event.pull_request.user.login }}
35+
PR_AUTHOR_AVATAR_PR: ${{ github.event.pull_request.user.avatar_url }}
36+
# pull_request_review_comment event
2737
PR_URL_REVIEW: ${{ github.event.pull_request.html_url }}
2838
PR_TITLE_REVIEW: ${{ github.event.pull_request.title }}
2939
PR_NUMBER_REVIEW: ${{ github.event.pull_request.number }}
40+
# issue_comment event
3041
PR_URL_ISSUE: ${{ github.event.issue.pull_request.html_url }}
3142
PR_TITLE_ISSUE: ${{ github.event.issue.title }}
3243
PR_NUMBER_ISSUE: ${{ github.event.issue.number }}
3344
run: |
34-
if [ "$EVENT_NAME" = "pull_request_review_comment" ]; then
45+
# Determine PR details based on event type
46+
if [ "$EVENT_NAME" = "pull_request" ]; then
47+
PR_URL="$PR_URL_PR"
48+
PR_TITLE="$PR_TITLE_PR"
49+
PR_NUMBER="$PR_NUMBER_PR"
50+
elif [ "$EVENT_NAME" = "pull_request_review_comment" ]; then
3551
PR_URL="$PR_URL_REVIEW"
3652
PR_TITLE="$PR_TITLE_REVIEW"
3753
PR_NUMBER="$PR_NUMBER_REVIEW"
@@ -41,65 +57,117 @@ jobs:
4157
PR_NUMBER="$PR_NUMBER_ISSUE"
4258
fi
4359
44-
# Extract repo short name from "owner/repo"
4560
REPO_SHORT="${REPO#*/}"
4661
47-
# Truncate comment body to 200 chars and add ellipsis if needed
48-
if [ ${#COMMENT_BODY} -gt 200 ]; then
49-
TRUNCATED_BODY="${COMMENT_BODY:0:200}..."
50-
else
51-
TRUNCATED_BODY="$COMMENT_BODY"
52-
fi
53-
5462
# Ensure bot has joined the channel
5563
curl -s -X POST "https://slack.com/api/conversations.join" \
5664
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
5765
-H "Content-Type: application/json" \
58-
-d "{\"channel\": \"$SLACK_CHANNEL_ID\"}"
66+
-d "{\"channel\": \"$SLACK_CHANNEL_ID\"}" > /dev/null
67+
68+
# Search channel history for an existing thread about this PR
69+
find_thread_ts() {
70+
HISTORY=$(curl -s "https://slack.com/api/conversations.history?channel=$SLACK_CHANNEL_ID&limit=200" \
71+
-H "Authorization: Bearer $SLACK_BOT_TOKEN")
5972
60-
# Build JSON payload to match GitHub's native Slack notification style
61-
PAYLOAD=$(jq -n \
62-
--arg channel "$SLACK_CHANNEL_ID" \
63-
--arg repo_short "$REPO_SHORT" \
64-
--arg pr_url "$PR_URL" \
65-
--arg pr_number "$PR_NUMBER" \
66-
--arg pr_title "$PR_TITLE" \
67-
--arg commenter "$COMMENTER" \
68-
--arg comment_body "$TRUNCATED_BODY" \
69-
--arg comment_url "$COMMENT_URL" \
70-
--arg avatar "$COMMENTER_AVATAR" \
71-
'{
72-
"channel": $channel,
73-
"username": $commenter,
74-
"icon_url": $avatar,
75-
"text": ($commenter + " commented on [" + $repo_short + "#" + $pr_number + "] <" + $pr_url + "|" + $pr_title + ">"),
76-
"unfurl_links": false,
77-
"blocks": [
78-
{
79-
"type": "section",
80-
"text": {
81-
"type": "mrkdwn",
82-
"text": ($commenter + " commented on [" + $repo_short + "#" + $pr_number + "] <" + $pr_url + "|" + $pr_title + ">")
73+
# Look for a message containing this PR's URL
74+
THREAD_TS=$(echo "$HISTORY" | jq -r --arg pr_url "$PR_URL" '
75+
.messages[]
76+
| select(.text != null and (.text | contains($pr_url)))
77+
| .ts' | tail -1)
78+
79+
if [ -n "$THREAD_TS" ] && [ "$THREAD_TS" != "null" ]; then
80+
echo "$THREAD_TS"
81+
fi
82+
}
83+
84+
if [ "$EVENT_NAME" = "pull_request" ]; then
85+
# PR opened/reopened — post a new top-level thread-starting message
86+
PAYLOAD=$(jq -n \
87+
--arg channel "$SLACK_CHANNEL_ID" \
88+
--arg repo_short "$REPO_SHORT" \
89+
--arg pr_url "$PR_URL" \
90+
--arg pr_number "$PR_NUMBER" \
91+
--arg pr_title "$PR_TITLE" \
92+
--arg author "$PR_AUTHOR_PR" \
93+
--arg avatar "$PR_AUTHOR_AVATAR_PR" \
94+
'{
95+
"channel": $channel,
96+
"username": $author,
97+
"icon_url": $avatar,
98+
"text": ("New pull request [" + $repo_short + "#" + $pr_number + "] <" + $pr_url + "|" + $pr_title + "> by " + $author),
99+
"unfurl_links": false,
100+
"blocks": [
101+
{
102+
"type": "section",
103+
"text": {
104+
"type": "mrkdwn",
105+
"text": ("New pull request [" + $repo_short + "#" + $pr_number + "] <" + $pr_url + "|" + $pr_title + "> by " + $author)
106+
}
83107
}
84-
},
85-
{
86-
"type": "rich_text",
87-
"elements": [
88-
{
89-
"type": "rich_text_quote",
90-
"elements": [
91-
{
92-
"type": "text",
93-
"text": $comment_body
94-
}
95-
]
108+
]
109+
}')
110+
111+
curl -s -X POST "https://slack.com/api/chat.postMessage" \
112+
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
113+
-H "Content-Type: application/json" \
114+
-d "$PAYLOAD"
115+
else
116+
# Comment event — try to find existing thread, then reply in it
117+
THREAD_TS=$(find_thread_ts)
118+
119+
# Truncate comment body to 200 chars
120+
if [ ${#COMMENT_BODY} -gt 200 ]; then
121+
TRUNCATED_BODY="${COMMENT_BODY:0:200}..."
122+
else
123+
TRUNCATED_BODY="$COMMENT_BODY"
124+
fi
125+
126+
PAYLOAD=$(jq -n \
127+
--arg channel "$SLACK_CHANNEL_ID" \
128+
--arg repo_short "$REPO_SHORT" \
129+
--arg pr_url "$PR_URL" \
130+
--arg pr_number "$PR_NUMBER" \
131+
--arg pr_title "$PR_TITLE" \
132+
--arg commenter "$COMMENTER" \
133+
--arg comment_body "$TRUNCATED_BODY" \
134+
--arg comment_url "$COMMENT_URL" \
135+
--arg avatar "$COMMENTER_AVATAR" \
136+
--arg thread_ts "$THREAD_TS" \
137+
'{
138+
"channel": $channel,
139+
"username": $commenter,
140+
"icon_url": $avatar,
141+
"text": ($commenter + " commented on [" + $repo_short + "#" + $pr_number + "] <" + $pr_url + "|" + $pr_title + ">"),
142+
"unfurl_links": false,
143+
"blocks": [
144+
{
145+
"type": "section",
146+
"text": {
147+
"type": "mrkdwn",
148+
"text": ($commenter + " commented on [" + $repo_short + "#" + $pr_number + "] <" + $pr_url + "|" + $pr_title + ">")
96149
}
97-
]
98-
}
99-
]
100-
}')
150+
},
151+
{
152+
"type": "rich_text",
153+
"elements": [
154+
{
155+
"type": "rich_text_quote",
156+
"elements": [
157+
{
158+
"type": "text",
159+
"text": $comment_body
160+
}
161+
]
162+
}
163+
]
164+
}
165+
]
166+
}
167+
| if $thread_ts != "" then .thread_ts = $thread_ts else . end')
101168
102-
curl -X POST "https://slack.com/api/chat.postMessage" \
103-
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
104-
-H "Content-Type: application/json" \
105-
-d "$PAYLOAD"
169+
curl -s -X POST "https://slack.com/api/chat.postMessage" \
170+
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
171+
-H "Content-Type: application/json" \
172+
-d "$PAYLOAD"
173+
fi

slack-app-manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"bot": [
1616
"chat:write",
1717
"chat:write.customize",
18-
"channels:join"
18+
"channels:join",
19+
"channels:history"
1920
]
2021
}
2122
}

0 commit comments

Comments
 (0)