GitHub PR Reply Guard
Goal
PR review thread への返信誤爆を防ぎ、返信結果を検証可能な形で残す。
Workflow
- PR と対象コメントを特定する
- 入力として PR 番号と対象コメント URL かコメント本文を受け取る。
- URL がある場合は
discussion_r<id>から候補 ID を抽出する。
- Review comment の一次情報を取得する
- 必ず
gh api repos/<owner>/<repo>/pulls/<pr>/commentsを使い、id,in_reply_to_id,path,body,html_urlを取得する。 pull_request_read.get_review_commentsのみで完結させない。ID が欠落する可能性がある。- URL が
discussion_r<id>の場合も、最終的な親 comment ID はこの一覧で確定する。
- 返信先 ID を確定する
- 優先順位:
html_url完全一致id一致path+ 本文冒頭一致(最終手段)
- 候補が複数ある場合は推定返信しない。候補一覧を出してユーザー確認する。
- GraphQL fallback を使う可能性があるため、必要なら review thread ID も取得する。
reviewThreads { nodes { id comments { nodes { databaseId url } } } }databaseId == parent comment idを含む thread を返信先 thread とする。
- 返信を投稿する
- 既定は review comment への reply API を使う。
- ただし
user_id can only have one pending review per pull requestなどで REST reply API が失敗した場合は、GraphQL のaddPullRequestReviewThreadReplyを使う。 - reply body は
\n文字列を埋め込まない。actual newline を渡す。- 推奨: here-doc で body を組み立てて
-F body="$body"で渡す - 非推奨:
-f body='line1\n\nline2'
- 推奨: here-doc で body を組み立てて
- 投稿結果を検証する
- REST reply API を使った場合:
- 返却の
in_reply_to_idが意図した親コメント ID と一致することを確認する。
- 返却の
- GraphQL thread reply を使った場合:
- 返却の
comment.urlを記録する。 - thread 再取得で、意図した thread に reply が追加されていることを確認する。
author,body,urlを再取得して thread 取り違えがないことを確認する。
- 返却の
html_urlまたはcomment.urlを必ず記録して、ユーザーへ共有する。
Guardrails
- 推測 ID で返信しない。
- PR 全体コメント (
issue comment) で代替する場合は、thread reply 不能な理由を明記する。 - pending review に遭遇したら、黙って issue comment に逃げない。先に thread reply 可能な経路を試す。
- 返信後の検証を省略しない。
- body の改行は投稿前に actual newline になっていることを確認する。
Output format
target: owner/repo#pr, parent comment idresult: posted / skipped / blockedproof: replyhtml_urlorcomment.url,in_reply_to_idorthread idnote: fallback 実施時の理由
Quick commands
# Review comment 一覧(ID付き)
gh api repos/<owner>/<repo>/pulls/<pr>/comments \
--jq '.[] | {id,in_reply_to_id,path,html_url,body}'
# 特定IDの確認
gh api repos/<owner>/<repo>/pulls/comments/<comment_id>
# review thread 一覧と parent comment の対応確認
gh api graphql -f query='
query {
repository(owner: "<owner>", name: "<repo>") {
pullRequest(number: <pr>) {
reviewThreads(first: 100) {
nodes {
id
comments(first: 20) {
nodes {
databaseId
url
}
}
}
}
}
}
}'
# actual newline を保って GraphQL thread reply
body=$(cat <<'"'"'EOF'"'"'
1行目
2行目
EOF
)
gh api graphql \
-f query='mutation($thread:ID!, $body:String!) { addPullRequestReviewThreadReply(input:{pullRequestReviewThreadId:$thread, body:$body}) { comment { url } } }' \
-f thread='<thread_id>' \
-F body="$body"