GitHub の親issue + sub-issue ハブ運用を AI 実装前提で言語化する
GitHub で「親 issue を議論ハブにして、確定した作業単位を sub-issue として 1 issue = 1 PR の粒度で切り出す」運用を感覚的にやっていたが、AI に sub-issue を実装させるようになって複数の事故が起きた。
人間だけで回していた頃は「親 issue を見れば文脈が分かる」「ラベルで状態を表せばいい」程度の暗黙ルールで動いていた。AI に sub-issue を渡したとたん、それが全部成立しなくなった。AI は親を見ない (見ろと言わないと見ない)、ラベルの慣習を察しない、要約された description を「最新の正」と信じる。結果、ボイラープレートを使うと決めたはずの sub-issue で AI が独自実装を始めたり、後追いで仕様補修 PR を出すハメになったりする。
このメモは、踏んだ地雷を 1 つずつ言語化して「あるべき形」をテンプレートと運用ガイドまで落とすための整理。
この運用パターンの呼び方
公式な単一名称はないが、近い用語:
- Tracking issue (Rust / Kubernetes 系で多用)
- Meta issue / Umbrella issue
- Epic (Jira 由来、GitHub Projects v2 でも使われる)
- Parent issue with Sub-issues (GitHub 純正機能、2024 以降 GA)
特徴は「親に PR を紐付けず、sub-issue 側に 1:1 で PR を切る」点。GitHub 純正の Sub-issues 機能を使うと、親 description に進捗バー (completed / total) が自動表示され、外部ツールなしで進捗が見える。
私が踏んだ 5 つの地雷
地雷 1: 元の要望が消える
一番痛かったのがこれ。
起票時に「こういう機能が欲しい」と書いた description は、議論が進むにつれて決定事項で上書きされていく。「ボイラープレートを使う」「データベースは Postgres にする」「認証は OAuth」… 気づくと description は 決定事項のリスト になっていて、起票時の生々しい「困りごと」が消えている。
数週間後に「そもそもなぜこれを作っているんだっけ」と振り返ろうとしても、そこに answer がない。GitHub は description の差分を保持していない (edit history はあるが、AI に渡しにくいし、人間でも追いにくい)。
特に痛いのは、後から仕様判断で迷ったときに「元要望に立ち返る」ができないこと。「これって本当に必要だっけ」を判定する根拠が消えている。
地雷 2: sub-issue 間の連携が弱い
親で「ボイラープレート X を使う」と決めた。それを前提に sub-issue A, B, C を切った。AI に sub-issue B を「実装して」と投げる。
AI は sub-issue B の description しか読まない。description には「ボイラープレートを使う」と書かれていない (親で決めたから)。結果、AI は 独自実装してしまう。
これは 2 回起きた。1 回目は気づいて差し戻し、2 回目は気づかずにマージされた。後追いで「実はボイラープレートを使うべきだった」という補修 PR を出すことになり、レビュー工数が二重にかかった。
問題の本質は、「親を読めば分かる」が AI には通用しない こと。人間も忘れるが、AI は最初から覚えていない。
地雷 3: ラベル運用が煩雑になる
最初は良かれと思って status:in-progress status:blocked status:review のような状態管理ラベルを作った。すぐに破綻した。
- close するとき、ラベルを外し忘れる
status:in-progressとstatus:blockedが両方付いている issue が出る- 「review」と「in-review」を間違えて作って、両方使われる
- ラベル一覧が肥大化して、本来の識別ラベル (
bugenhancement) が埋もれる
そもそも GitHub の issue には state (open/closed) があり、close reason もあり、Projects v2 のステータス列もある。状態を表す手段が複数あるのに、さらにラベルで重ねるのが間違い。
地雷 4: 親で議論するか、議論用 sub-issue を切るか方針が定まらない
「とりあえず親で議論」していたが、議論が長くなるとコメント欄が 50 件 100 件と肥大化する。後から探したい論点が埋もれる。
かといって最初から議論用 sub-issue を切るのも重い。「議論」と「実装 sub-issue」をどう使い分けるかの基準がなく、毎回判断ブレが起きる。
地雷 5: close 理由が残らない
gh issue close 209 を素で叩くと、「なぜ閉じたか」が記録されない。実装完了で閉じたのか、仕様変更で不要になったのか、別 issue に統合したのか、振り返り時に判別できない。
数ヶ月後に「これってなんで close されたんだっけ」を確認するために、関連する PR や Slack を漁ることになる。
あるべき形
5 つの地雷それぞれに、対応する規約を定める。
規約 1: 元要望は first comment に凍結する (ハイブリッド方式)
description を大改編する 前に、自分の最初のコメントとして元 description 全文を貼り付ける。description 側は要約 + 全文へのリンクに置き換える。
## 元の要望
(議論で書き換わるため要約のみ)
詳細は [first comment](https://.../#issuecomment-XXXXX) を参照。これで:
- description は decision log の最新状態を表す (生きている)
- first comment は起票時の意図を凍結する (死んでいるが、永遠にそこにある)
両方の責務を分離できる。これは「マスト」運用にする。「気が向いたら」だと忘れる。
gh issue で起票するときは、最初に description を書いて、すぐに同じ内容を first comment に投稿し、description を要約に書き換える、を 1 連の操作にする。
規約 2: sub-issue は self-contained にする (3 層防御)
DRY を多少犠牲にしても、sub-issue 単体で AI が実装に着手できる状態にする。具体的には:
| 層 | 役割 | 場所 |
|---|---|---|
| 1 | 中央台帳 (Decision Log) | 親 issue の description |
| 2 | 依存関係の明示 | sub-issue の Depends on: |
| 3 | 前提仕様の転記 | sub-issue の 前提仕様 セクション |
「親を見れば分かる」と書いて済ませない。親で決まった前提を sub-issue 内に転記する。リンクだけ貼っても AI はリンク先を読まない (読ませる指示を別途仕込まない限り)。
前提が変わったら、3 層全部を更新する。これは手間だが、手間をかけたぶん事故が減る。逆に手間をかけなければ、必ずどこかが古い情報のまま残って AI を誤誘導する。
sub-issue テンプレに「⚠️ 実装前に必ず読むこと」セクションを置き、親へのリンクと「前提仕様セクションが正、リンク先は参考」と明記する。
規約 3: 状態は description 先頭に太字テキストで書く (ラベルは使わない)
**Status:** 実装フェーズ (sub-issue #209 で実装中)これだけ。状態管理ラベル (status:*) は 作らない。識別ラベル (bug / enhancement / documentation) は最小限に絞れば OK。
理由:
- 1 箇所にしか書かないので食い違いが起きない
- AI が description を読めば必ず状態が見える
- ラベル運用の煩雑さから解放される
- close 時に「外し忘れ」が発生しない
Projects v2 を使っている場合は、Projects v2 のステータス列と Status 行を二重管理することになるが、Projects v2 は「複数 issue を俯瞰する用」、Status 行は「issue 単体を見たときの状態」と割り切る。
規約 4: emoji は必ずテキスト併記 + 凡例固定
AI 解釈と検索の安定性のため、凡例を固定する:
- ✅ closed
- ⏳ in-progress
- 🔴 blocked
- ⏸ on-hold
✅ 単独ではなく ✅ closed と書く。✓ ☑ done 等の揺れは禁止。grep でも引きやすくなる。
規約 5: close は GitHub 純正 reason + コメント併記
gh issue close 209 --reason completed --comment "実装完了。詳細は #PR210"
gh issue close 207 --reason "not planned" --comment "仕様変更により不要"
gh issue close 206 --reason duplicate --comment "#208 に統合"3 種類しかない:
completed— 当初の目的を達成して閉じるnot planned— やらないと決めて閉じるduplicate— 別 issue に統合して閉じる
これだけ。close ラベルでの分類はやらない。
--comment を必ず付ける。理由がないまま閉じると、後から振り返れない。
規約 6: PR には Refs: #親 を使う (Fixes: ではない)
Fixes: #208 を書くと PR マージで親 issue が close されてしまう。親はハブとして残したいので、Refs: #208 を使う。Fixes: は sub-issue (1:1 で対応する作業単位) にだけ使う。
PR テンプレに Affects: #X, #Y 欄を入れておくと、「この変更が他 sub-issue の前提を変えるか」を意識的に書ける。これが書かれていれば、変更が他 sub-issue に波及するときに 3 層防御 (規約 2) を更新するトリガーになる。
規約 7: 議論用 sub-issue は「論点が 1 つに絞れる」ときだけ切る
議論を sub-issue に切る基準:
- 論点が 1 つに絞れる (タイトルで論点を表現できる)
- 親と並行して議論を進めたい (親の議論を止めたくない)
- 決着までに 5 コメント以上やり取りが見込まれる
それ以外は親で議論する。議論用 sub-issue は決着したら 必ず close する (open のまま放置しない)。
決着内容は親の Decision Log に転記する。議論用 sub-issue は「議事録」として残るが、最新の正は親側にある。
テンプレート構成
実際にボイラープレートに落とすときの構成:
meta/github-workflow/
├── README.md # 概要 + install.sh 使い方
├── parent-issue.md # 親 issue テンプレ (ハイブリッド方式)
├── sub-issue.md # sub-issue テンプレ (self-contained)
├── discussion-issue.md # 議論用 sub-issue テンプレ
├── PR-template.md # Affects 欄入り
├── issue-workflow.md # 運用ガイド (判断を残さない)
└── install.sh # curl 配信用なぜ .github/ISSUE_TEMPLATE/ には置かないか
ありがちな構成は .github/ISSUE_TEMPLATE/ 直下にテンプレを置くことだが、ボイラープレート用リポでは 置かない。理由:
- dogfooding したくない — テンプレ管理用リポであり、サンプル issue を起票したくない。
.github/ISSUE_TEMPLATE/に置くと、自分で issue を立てるときにテンプレ選択画面が出てしまう - 二重管理を避ける — 配布物 (
.github/) と原典 (meta/) を両方持つと同期チェックが必要になる - CI を増やしたくない — 同期チェック workflow を作る運用コストを払いたくない
代わりに meta/github-workflow/ に原典を集約して、各リポは install.sh で .github/ISSUE_TEMPLATE/ に展開する。
install.sh の方針
curl -sSL https://raw.githubusercontent.com/<user>/<repo>/main/meta/github-workflow/install.sh | sh設計判断:
- 引数なし —
--force--dest等を増やさない。シンプルさを優先 - current directory の
.github/ISSUE_TEMPLATE/に展開 — 暗黙の規約だが、誰もが期待する場所 - 既存ファイルを検出したら警告して停止 — 上書きしない。ユーザーが自分でマージする
- バージョニングなし — ボイラープレートは配って終わり。各リポで成長する。「v2 にアップグレード」を考えない
- CI sync チェックなし — dogfooding しないので、原典側の変更を配布先に強制する仕組みは持たない
運用ガイドの設計原則: 「読み手に判断させない」
issue-workflow.md には以下を 書かない:
- 「お好みで」
- 「必要に応じて」
- 「考えてみてください」
- 「適宜」
- 「状況に応じて」
理由: 判断を残すと、人によって運用がブレる。ブレた運用は AI を誤誘導する。
代わりに:
- コピペで動く
ghコマンドを完備 - 失敗時の対処を必ず明記 (「~~ が起きたら、こうする」)
- チェックリスト形式で「やった/やってない」を可視化
- アンチパターン集を別セクションで列挙
ガイドの 7 セクション構成:
- このフローの全体像
- 親 issue を起票する (ハイブリッド方式の手順)
- 親 issue で議論する (Decision Log の書き方)
- sub-issue に分解するか判断する (基準)
- sub-issue を実装フェーズに進める (3 層防御の徹底)
- アンチパターン集
- 既存リポへの展開 (install.sh)
アンチパターン集
これは何度も踏むので明文化しておく:
- ❌
Fixes: #親issueで PR を出す → 親が close される - ❌ issue を削除する → 議論の履歴が永遠に失われる、絶対禁止
- ❌
--no-verifyで PR push → pre-commit hook を通さない - ❌ decision を issue コメントだけに残す → description の Decision Log に転記する
- ❌ 状態管理ラベル (
status:*) を作る → description の Status 行を使う - ❌ sub-issue の前提仕様をリンクだけで済ませる → 転記する (3 層防御)
- ❌ 議論用 sub-issue を open のまま放置 → 決着したら close する
- ❌ close を
--commentなしで実行 → 必ず理由を付ける - ❌ description の元要望を上書きしてから後悔 → 大改編の前に first comment に貼る
- ❌ AI に sub-issue を投げて、親 issue へのリンクだけ渡す → 前提仕様を sub-issue 内に転記する
まとめ
感覚運用で踏んだ 5 つの地雷:
- 元の要望が消える
- sub-issue 間の連携が弱い
- ラベル運用が煩雑になる
- 親で議論するか sub-issue を切るか定まらない
- close 理由が残らない
それぞれに対応する規約:
- first comment に元要望を凍結 (ハイブリッド方式)
- 3 層防御 (Decision Log / Depends on / 前提仕様の転記)
- 状態は description 先頭の太字テキスト (ラベル使わない)
- 議論用 sub-issue は論点 1 つに絞れるときだけ
gh issue close --reason --commentを必須化
加えて:
- emoji + テキスト併記、凡例固定
- PR は
Refs: #親(Fixes:は sub-issue にのみ) - テンプレ原典は
meta/配下に集約 (.github/には置かない) - 運用ガイドは「判断を残さない」原則で書く
鍵は 元要望の凍結 と sub-issue の self-contained 化 の 2 点。GitHub 純正の Sub-issues + Reason close + first comment 凍結を組み合わせれば、外部ツールなしでだいたい運用できる。AI に sub-issue を投げる前提なら、self-contained 化に手間をかけるほうがレビュー工数より圧倒的に安い。