tech AI generated (Claude)

GitHub Issue を閉じるだけでブログ記事が自動生成される仕組みを作った

GitHub Issue のクローズをトリガーに、AI が技術ブログ記事を自動生成して PR を作成する仕組みの設計と実装を解説します。

#GitHub Actions #自動化 #AI #ブログ運営 #Cowork #CI/CD

GitHub Issue を閉じたら記事ができる — そんな仕組みを作ろう

技術ブログを運営していると、「Issue を解決したのに記事にする時間がない」という問題に直面します。せっかくの知見がリポジトリの中に埋もれたまま、誰の目にも触れない。この課題を解決するために、GitHub Issue のクローズをトリガーにして AI がブログ記事を自動生成し、PR として提出する仕組みを構築できます。

この記事では、設計の考え方から実装中に遭遇した3つの大きな壁、そしてその解決策まで具体的に解説します。

全体アーキテクチャ

処理の流れは次のとおりです。

Issue クローズ
  → GitHub Actions がラベル(blog:queued)を付与
  → スケジュールタスク(Cowork 等の定期実行基盤、または GitHub Actions の scheduled workflow)が定期的にキューを確認
  → AI が Issue メタデータを元に記事を生成
  → feature ブランチで PR を作成
  → 人間がレビュー → マージ → 自動デプロイ

ポイントは「完全自動だが、公開前に人間のレビューが入る」という設計です。AI が生成した記事をそのまま公開するのではなく、PR というレビューゲートを設けることで品質を担保しています。

記事タイプのラベル設計

Issue に付けるラベルで記事の種類を制御します。

blog:tech-article は 2000〜4000字の本格的な技術ブログ記事を生成します。SEO を意識したタイトルや description、構造化された見出しを含みます。一方、blog:dev-log は 500〜1500字の簡潔な開発ログで、「何をやったか」を事実ベースで記録します。

記事にしたくない Issue には blog:skip を付けておけばスキップされます。処理済みの Issue には自動的に blog:generated が付与され、二重生成を防ぎます。

GitHub Actions × AI 記事生成の実装で直面した3つの壁

FUSE マウントで git 操作が失敗する問題と回避策

サンドボックス環境(Cowork など)からユーザーのフォルダにアクセスする仕組みは、内部的に FUSE(Filesystem in Userspace)マウント(virtiofs)を使用している場合があります。このマウントではファイルの作成と書き込みはできますが、ファイルの削除(unlink)ができないという制限があります。

git はほぼすべてのコマンドで .lock ファイルを作成し、完了後に削除します。削除できないため、git addgit commitgit checkout もすべてエラーになりました。

fatal: Unable to create '/path/.git/index.lock': Function not implemented

解決策として、サンドボックス内部(削除が自由にできる領域)にリポジトリを丸ごとクローンし、そこで git 操作を完結させるようにしました。FUSE マウント側のファイルはあくまで「閲覧用」として扱い、git 操作は一切行いません。

ブランチ保護ルールと GitHub Actions GITHUB_TOKEN の権限制約

最初の設計では、Issue がクローズされたら GitHub Actionsblog-queue/ フォルダに JSON ファイルを書き込み、それを記事生成の待ちリストとして使う予定でした。

# 当初の設計(失敗)
- name: Write queue file
  run: |
    echo "$METADATA" > blog-queue/$ISSUE_NUMBER.json
    git add blog-queue/
    git commit -m "chore: queue blog request"
    git push origin develop

ところが、保護されたブランチ(developmain など)に対しては、GitHub Actions のデフォルトトークン(GITHUB_TOKEN)では直接 push が拒否されます。

remote: error: GH006: Protected branch update failed

解決策として、ファイルベースのキューを完全に廃止し、GitHub のラベルをキューとして使う設計に切り替えました。Issue がクローズされたら blog:queued ラベルを付けるだけ。ラベル操作は Issues API で完結するため、ブランチ保護の影響を受けません。

# 最終設計(成功)
- name: Queue blog request via label
  uses: actions/github-script@v7
  with:
    script: |
      await github.rest.issues.addLabels({
        owner, repo,
        issue_number: issueNumber,
        labels: ['blog:queued']
      });

記事生成側は gh issue list --label "blog:queued" でキューを検索するだけです。シンプルで堅牢な設計になりました。

workflow_dispatch がデフォルトブランチでしか使えない制約

手動で記事生成をトリガーするために workflow_dispatch イベントを使っています。これにより、GitHub の UI やAPI から任意の Issue 番号を指定して記事生成を要求できます。

しかし、この機能にはひとつ落とし穴があります。ワークフローの YAML ファイルがデフォルトブランチ(main)に存在しない限り、GitHub の UI にも API にも表示されず、実行できません。

develop ブランチにマージしただけでは使えず、main にもマージして初めて workflow_dispatch が利用可能になります。これは GitHub の仕様で、ワークフロー一覧の取得がデフォルトブランチのみを参照するためです。

スケジュールタスクによるコスト削減

記事生成には AI を使いますが、定期実行基盤の選び方によってはコストを抑えられます。たとえば、Cowork のサブスクリプションに含まれるスケジュールタスク機能を使えば、30分間隔でキューを確認・処理しても追加の API 課金が発生しません。GitHub Actions の schedule トリガーを使う場合も、パブリックリポジトリでは無料枠内で十分対応できます。

スケジュールタスクが毎回新しいサンドボックスで実行される場合は、gh CLI のインストールや認証を毎回行う必要があります。この手順をタスク定義ファイル(AI エージェントに実行手順を伝えるドキュメント)に明記しておくと、タスクが自律的にセットアップできるようになります。

生成される記事の品質管理

AI が生成する記事には以下の品質基準を設けています。

Astro Content Collections のフロントマターには sourceIssue フィールドで元の Issue 番号を記録し、aiGenerated: true で AI 生成であることを明示します。tech-article は 2000〜4000字、dev-log は 500〜1500字の文字数範囲を設定し、冗長すぎず薄すぎない記事を目指しています。

SEO 面では、タイトルにメインキーワードを含め 30〜60字に収める、description を 120字以内にする、見出し(H2, H3)にサブキーワードを含めるといったルールを適用しています。

まとめ

GitHub Issue のクローズから記事生成までを自動化することで、「知見の蓄積」と「ブログ運営」を同時に回せる基盤が手に入ります。FUSE の制限、ブランチ保護との衝突、GitHub の仕様上の制約という3つの壁がありますが、ラベルベースのシンプルな設計に切り替えることで回避できます。

今後は記事のテンプレートを充実させ、カテゴリごとに最適化された記事生成を目指していきます。

関連記事