🪅

GitHub Actions × OpenHandsでプロジェクトの自動改善提案Botを作ってみた

2025/03/02に公開

はじめに

個人開発していると、最初は勢いよく作り始めても、しばらくすると「これで本当に合ってるのか?」とか「もっとUI/UXを最適化できるんじゃ?」といった客観的な視点がどんどん薄れていくのを感じることがありました。

OpenHandsというOSSを試してみました。
多分本来はプルリクを作る用途が一般的だと思いますが、今回は改善提案を行うBotとして扱います

この記事では、OpenHandsをGitHub Actionsから呼び出してリポジトリ全体のコードを分析し、改善提案をIssueとして自動生成してくれるBotを作ってみた事例を紹介します。
個人開発でもサクッと導入できたので、同じように「ひとりで開発しているけど、他の視点も欲しい!」という方の参考になれば嬉しいです。

OpenHandsとは?

DevinのOSS版的なものと言われているのをよく目にします。
OSSなので、自前で好きなLLM APIを用意すればOK。
今回はOpenRouter経由でClaudeのSonnet 3.7を呼び出していて、1回のワークフロー実行あたり1ドル程度で複数のIssueを生成してもらうようにしました。

個人プロジェクトだと頻繁に回さないので、このぐらいのコスト感なら無理なく使えるかなと思っています。

導入した背景・プロジェクト概要

今回OpenHandsを入れてみたのは、Chrome拡張 + API + フロントエンドを組み合わせた個人ツールを作っていたのがきっかけです。
すべて1人でやっているのですが、いざ完成が近づくと「別の観点から見たら何が足りないか?」が気になってきました。

個人開発あるあるとして、自分が想定している範囲だけはリファクタできても、全体を俯瞰した指摘をもらう機会ってほぼゼロに近いと思います。

そこで「AIに任せたら客観的に提案してくれるんじゃ?」と考え、今回OpenHandsを試してみました。

GitHub ActionsでOpenHandsを呼び出す手順

OpenHands公式リポジトリにopenhands-resolver.ymlというサンプルがあるので、それをベースにワークフローを構成しました。具体的な流れは以下の通りです。

1. Secretsの設定

  1. LLM_API_KEY: OpenRouterなど好きなLLMのAPIキー

  2. PAT_TOKEN: リポジトリのIssueやPRを作成するためのPersonal Access Token

(repo権限・workflow権限が必要)

  1. PAT_USERNAME: 上記トークンの発行ユーザー

これらをリポジトリのSettings > Secrets and variables > ActionsでRepository secretsとして登録します。

2. ワークフローファイルの作成

.github/workflows/配下に openhands-suggestions.yml など、わかりやすい名前でファイルを作り、ざっくり以下のように書きます。

name: OpenHands Project Suggestions

on:
  workflow_dispatch:

permissions:
  contents: read
  issues: write

jobs:
  suggest-improvements:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install OpenHands
        run: |
          python -m pip install --upgrade pip
          pip install openhands-ai

      - name: Export LLM settings
        env:
          LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
          LLM_MODEL: ${{ secrets.LLM_MODEL || 'anthropic/claude-3-5-sonnet-20241022' }}
          LLM_BASE_URL: ${{ secrets.LLM_BASE_URL || '' }}
        run: |
          echo "WORKSPACE_BASE=${{ github.workspace }}" >> $GITHUB_ENV
          echo "SANDBOX_USER_ID=$(id -u)" >> $GITHUB_ENV

      - name: Run OpenHands suggestion agent
        env:
          LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
          LLM_MODEL: openrouter/anthropic/claude-3-7-sonnet
          LLM_BASE_URL: https://openrouter.ai/api/v1
          WORKSPACE_BASE: ${{ env.WORKSPACE_BASE }}
          SANDBOX_USER_ID: ${{ env.SANDBOX_USER_ID }}
        run: |
          python -m openhands.core.main -t "リポジトリ全体を分析してコード品質やドキュメントの改善提案を行ってください。修正は行わず、提案を箇条書きのMarkdown形式でまとめ、SUGGESTIONS.mdとして保存してください。"

      - name: Create separate GitHub Issues for each suggestion
        uses: actions/github-script@v7
        env:
          GITHUB_TOKEN: ${{ secrets.PAT_TOKEN || github.token }}
        with:
          script: |
            const fs = require('fs');

            // SUGGESTIONS.mdファイルの読み込み
            const suggestions = fs.existsSync('SUGGESTIONS.md')
              ? fs.readFileSync('SUGGESTIONS.md', 'utf8')
              : '';

            if (!suggestions) {
              console.log('提案内容が見つかりませんでした。スキップします。');
              return;
            }

            const issues = [];
            let currentTitle = '';
            let currentBody = [];

            // ファイルを行ごとに読み込み、## の行をタイトル、箇条書きを本文として振り分け
            suggestions.split('\n').forEach(line => {
              if (line.startsWith('## ')) {
                if (currentTitle && currentBody.length > 0) {
                  issues.push({ title: currentTitle, body: currentBody.join('\n') });
                }
                currentTitle = line.replace('## ', '').trim();
                currentBody = [];
              } else if (line.startsWith('1.') || line.startsWith('- ') || line.match(/^\d+\./)) {
                currentBody.push(line);
              } else if (line.trim() !== '' && currentBody.length > 0) {
                currentBody.push(line);
              }
            });

            // 最後のグループを追加
            if (currentTitle && currentBody.length > 0) {
              issues.push({ title: currentTitle, body: currentBody.join('\n') });
            }

            // 個別にIssueを作成
            for (const issue of issues) {
              await github.rest.issues.create({
                owner: context.repo.owner,
                repo: context.repo.repo,
                title: `[改善提案] ${issue.title}`,
                body: issue.body
              });
            }

ポイント:

  • on: workflow_dispatch にしておけば、Actionsタブで手動実行できるようになります。
  • もちろん定期的に走らせたい場合はschedule+cronでもOK。
  • Settings > Actions > General からWorkflow permissionsをRead and write permissionsにしておき、「Allow GitHub Actions to create and approve pull requests」もONにする。

Claude 3.7 sonnet × OpenRouterを選んだ理由とコスト感

実際に使ってみた感じ、1回回すと複数Issueを一度に作ってくれるので十分便利でした。
コストは1回あたり1~1.5ドルぐらいのイメージです。
個人開発なら毎日回すわけでもないし、そこまで出費は大きくならないのがありがたいです。

Claude系のモデルは長いコンテキストを扱うのが得意と言われていて、コードの意図を把握するのも結構上手い印象を受けています。
OpenRouterを使えばGPT-4やほかのモデルも試せますが、ひとまずClaudeで進めています。もちろんモデルを切り替えるのも難しくないので、好みのモデルに置き換えるのもアリだと思います。

生成されたIssueのイメージ

ワークフローを実行すると、こんな感じでIssueがいくつか自動生成されます。

例: [改善提案] Chrome拡張機能の改善点

設定オプションの追加
- APIエンドポイントのURLをユーザーが設定できるようにする
- 送信前の確認ダイアログの導入

エラー処理の強化
- ネットワークエラーやAPIエラーの詳細表示
- リトライ機能検討

セキュリティ強化
- Content Security Policy (CSP) 導入
- 必要最小限の権限の見直し (manifest.json)

UIの改善
- ダークモード対応、デザインの最適化
- background.jsを複数ファイルに分割して責務を整理
  1. 全部バーッと読んでやりたいタスクとやらないタスクを振り分け
  2. 必要に応じて細かくIssueを整理し直す

という感じで活用中です。

導入してみた所感

  • 客観的な提案が自動で得られる
  • コスト調整が楽
    • 1回1ドル程度でした。

まとめ

今回は、個人開発に客観的な視点が欲しいときにOpenHands × GitHub Actionsで「自動改善提案Bot」を作った事例を紹介しました。個人・小規模プロジェクトだとどうしても主観に偏りがちなので、こんな感じでAIの“客観的な改善案”を気軽に取り入れられるのは割と助かります

  • 個人利用なら1回あたりのコストを自分でコントロールしやすい
  • カスタマイズでよりプロジェクト特化した提案をさせることも可能
  • もし合わなければ削除や変更も自由で、初期投資がほぼ不要
  • Issueを作る用途だけじゃなくて、本来のプルリクを作る用途で利用もできる

Devinのようにそこまで大げさな仕組みは要らないけど「とりあえず客観的なフィードバックが欲しい!」という方は、ぜひ試してみてください。
私としては、意外な観点からのアドバイスもあって開発が楽しくなりました。

Discussion