Issueの誤自動クローズを防ぐ、トリアージボットとスウィープの二重安全策
Issueに人間がコメントしても自動クローズされてしまうバグを修正するため、トリアージワークフローとスウィープスクリプトの両方に防護処理が追加されました。人間のアクティビティを確実に検出する二重の安全策により、アクティブなIssueの誤クローズを防ぎます。
背景
#16497 として報告された通り、「60日間の非アクティブ」を理由に大量のIssueが誤ってクローズされる問題が発生していました。ユーザーが stale 警告に「まだ関連性がある」とコメントしても、その後も自動クローズが実行されるという状況です。
問題の根本原因は3つあります。第一に、トリアージボット(claude-issue-triage.yml)が stale および autoclose ラベルの存在を認識しておらず、人間がコメントしてもこれらのラベルを除去する処理がありませんでした。第二に、sweep.ts の closeExpired() 関数がライフサイクルラベルの付与日時と経過時間しか確認せず、ラベル付与後に人間がコメントしたかどうかを無視していました。第三に、10件以上のアップボートによる保護がenhancementラベルのIssueにのみ適用されており、人気の高いバグ報告でも自動クローズされる可能性がありました。
#11792 は、stale警告の後に3件の人間コメントがあったにもかかわらず自動クローズされた具体例として挙げられています。
技術的な変更
変更は claude-issue-triage.yml と scripts/sweep.ts の2ファイルにわたり、それぞれ独立した防護層を構成しています。
トリアージワークフローへの stale/autoclose ラベル認識の追加では、まず許可ラベルリストに stale と autoclose を追加しました。
# 変更前
Lifecycle: needs-repro, needs-info
# 変更後
Lifecycle: needs-repro, needs-info, stale, autoclose
さらに、issue_comment イベント処理のプロンプト指示に、人間コメント時にこれらのラベルを除去する命令が追加されました。
# 追加された指示
- If the issue has `stale` or `autoclose`, remove the label — a new human comment means the issue is still active:
`gh issue edit ${{ github.event.issue.number }} --remove-label "stale" --remove-label "autoclose"`
あわせて、needs-repro / needs-info ラベルの除去条件に関するコメントも「実質的な詳細が提供された場合のみ needs-repro または needs-info を除去する」と明確化され、stale/autoclose との混同を防いでいます。
sweep.ts への安全ネット処理は2点の変更で構成されます。closeExpired() に、ラベル付与後の人間コメント存在チェックが追加されました。
// 追加されたコード
const comments = await githubRequest<any[]>(
`${base}/comments?since=${labeledAt.toISOString()}&per_page=100`
);
const hasHumanComment = comments.some(
(c) => c.user && c.user.type !== "Bot"
);
if (hasHumanComment) {
console.log(
`#${issue.number}: skipping (human activity after ${label} label)`
);
continue;
}
また、アップボート保護については、markStale() と closeExpired() の両方で isEnhancement による条件分岐が削除され、すべてのIssueタイプに一律に適用されるようになりました。
// 変更前(markStale内)
const isEnhancement = issue.labels?.some(
(l: any) => l.name === "enhancement"
);
const thumbsUp = issue.reactions?.["+1"] ?? 0;
if (isEnhancement && thumbsUp >= STALE_UPVOTE_THRESHOLD) continue;
// 変更後
const thumbsUp = issue.reactions?.["+1"] ?? 0;
if (thumbsUp >= STALE_UPVOTE_THRESHOLD) continue;
設計判断
トリアージワークフローと closeExpired() の両方に保護処理を置く「二重防御」の設計が採用されました。
理想的にはトリアージワークフローがラベルを除去するため、closeExpired() が人間コメントを確認する必要はないはずです。しかし、スウィープが実行されるタイミングとトリアージボットがコメントを処理するタイミングの間に競合状態が発生し得ます。人間がコメントした直後にスウィープが走った場合、トリアージボットがまだラベルを除去していなければ誤クローズが起きます。PR本文でも「The triage workflow should remove lifecycle labels on human activity, but check here too as a safety net.」と明記されており、スウィープ側のチェックは意図的な安全ネットとして位置付けられています。
アップボート保護の全タイプ拡張は、バグ報告を含むすべての人気Issueを平等に保護するという方針の明確化です。enhancementに限定していた元の実装は、バグ報告への適用漏れという非対称性を生んでいました。
まとめ
トリアージボットへのラベル認識追加と closeExpired() の安全ネットにより、競合状態を含む様々なタイミングで人間のアクティビティを見逃さない設計が実現しました。人間コメントを「Botでないユーザーのコメント」として定義する単純な判定が、複雑なワークフロー間の同期問題をシンプルに解決しています。