🤖

ChatGPTからGitHubにIssueを作成する為のマイGPTの設定

に公開

皆様はオニギリをフォークやスプーンで食べてみた事はありますか?ただの冷飯でびっくりするので一度試してみてください。

びっくりするような導入ですが、何が言いたいかというと使う道具によって思考や想像力って結構変わりますよねという話です。タイプライターで打ち込むか、口述を筆記で書き写すか、それは物語自体に影響を与えます。

そしてAIにも色々なUIがありますが、今のところアイディアを練ったり仕様を検討したりするのに適しているのはブラウザで開くチャット形式の対話型AIな気がしています。

という事でChatGPTさんと仕様の議論や、新たな機能についての検討をしていると、もろもろ結論に達して「今の議論の内容をIssueにして欲しいぞ!」というタイミングが頻繁に出てきます。
コピペも面倒だったのでマイGPTで自身のリポジトリと連携したら快適になったので覚え書きです。

普段の開発フロー

自分の最近の開発フローはこんな感じです

  1. 仕様や実行計画についてAIさんと相談
  2. 内容をissueにまとめる
  3. ClaudeCodeさんにissueを読んで実装してもらう

この記事でできるようになること

  • ChatGPTから自分のリポジトリに対してIssueを作成
  • いつでもどこからでもアイデアが浮かんだらIssueを作れる環境構築
  • AIさんとの議論をそのままタスク化

注意点として、Plus / Team / Enterpriseの課金ユーザーが対象です。
無料ユーザーはカスタムGPTを作る事ができません。

手順

では実際の設定手順を説明します。ここでは自分のGitHub ID pppp606 を例にし、公開リポジトリのみに権限を与える設定で進めます。
組織のリポジトリにも権限は与えられますが慎重に取り扱う事をお勧めします。

1. GitHub側の準備

まずGitHub OAuth Appを作成します。

  1. GitHub → OAuth Apps 作成ページ
  2. 「New OAuth App」をクリック
  3. 以下の情報を入力:
    • Application name: 任意(例: ChatGPT Issue Assistant)
    • Homepage URL: 空欄 or https://chat.openai.com
    • Authorization callback URL: 一旦ダミーでOK(例:https://chat.openai.com/aip/placeholder
  4. 作成後、Client IDClient Secret を控えておく

2. ChatGPT側でCustom GPTを作成

次にChatGPT側の設定です。

  1. ChatGPT → My GPTs → Create → Configure
  2. 基本情報を入力:
    • 名前: GitHub Issue Assistant
    • 説明: pppp606リポジトリのIssueを作成・編集するアシスタント
    • 指示: 以下のような内容を記載
あなたは私専用のGitHub Issueアシスタントです。
- Issueの一覧取得
- Issueの作成(タイトルと本文は私と相談してから確定)
- Issueの取得・更新
- リポジトリのファイル読取
GitHub上での操作はpppp606配下のリポジトリに限定する。
公開リポのコードやREADMEを読むのは許可する。

3. Actionsの設定

APIとの連携設定を行います。

  1. 新しいアクションを作成する → スキーマ にOpenAPI JSONを貼り付け
    • issuesの読み込み,変更,作成、リポジトリの読み込みのエンドポイントを渡しています
{
  "openapi": "3.1.0",
  "info": { "title": "GitHub Issues ops + file read", "version": "0.0.5" },
  "servers": [{ "url": "https://api.github.com" }],
  "components": {
    "schemas": {},
    "securitySchemes": {
      "oauth": {
        "type": "oauth2",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://github.com/login/oauth/authorize",
            "tokenUrl": "https://github.com/login/oauth/access_token",
            "scopes": {
              "public_repo": "Access public repositories",
              "repo": "Access private repositories"
            }
          }
        }
      }
    }
  },
  "security": [ { "oauth": ["public_repo"] } ],
  "paths": {
    "/repos/{owner}/{repo}/issues": {
      "get": {
        "operationId": "listIssues",
        "summary": "List issues",
        "parameters": [
          { "name": "owner", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "repo",  "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "state", "in": "query", "required": false, "schema": { "type": "string", "enum": ["open","closed","all"] } },
          { "name": "labels", "in": "query", "required": false, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "type": "object" } }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createIssue",
        "summary": "Create a new issue",
        "parameters": [
          { "name": "owner", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "repo",  "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["title"],
                "properties": {
                  "title": { "type": "string" },
                  "body":  { "type": "string" },
                  "labels": { "type": "array", "items": { "type": "string" } },
                  "assignees": { "type": "array", "items": { "type": "string" } }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "integer" },
                    "number": { "type": "integer" },
                    "title": { "type": "string" },
                    "body": { "type": "string" },
                    "html_url": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/repos/{owner}/{repo}/issues/{issue_number}": {
      "get": {
        "operationId": "getIssue",
        "summary": "Get a single issue",
        "parameters": [
          { "name": "owner", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "repo",  "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "issue_number", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "integer" },
                    "number": { "type": "integer" },
                    "title": { "type": "string" },
                    "body": { "type": "string" },
                    "state": { "type": "string" },
                    "html_url": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateIssue",
        "summary": "Update an issue",
        "parameters": [
          { "name": "owner", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "repo",  "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "issue_number", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": { "type": "string" },
                  "body":  { "type": "string" },
                  "state": { "type": "string", "enum": ["open","closed"] },
                  "labels": { "type": "array", "items": { "type": "string" } },
                  "assignees": { "type": "array", "items": { "type": "string" } }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "integer" },
                    "number": { "type": "integer" },
                    "title": { "type": "string" },
                    "body": { "type": "string" },
                    "state": { "type": "string" },
                    "html_url": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/repos/{owner}/{repo}/contents/{path}": {
      "get": {
        "operationId": "getFileContents",
        "summary": "Read file contents (base64 in 'content')",
        "parameters": [
          { "name": "owner", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "repo", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "path", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "ref",  "in": "query", "required": false, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": { "type": "string" },
                    "path": { "type": "string" },
                    "content": { "type": "string" },
                    "encoding": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
  1. 認証 → OAuth を選択して以下を設定:

    • 認証URL: https://github.com/login/oauth/authorize
    • トークンURL: https://github.com/login/oauth/access_token
    • Client ID / Secret: GitHub OAuth Appからコピー
    • Scope: public_repo(プライベートリポも扱う場合はrepo
  2. アクションを保存すると固有のCallback URLが表示される

4. GitHub側のCallback URLを差し替え(ハマりどころ!)

ここが一番のハマりどころです。正しいCallback URLは、GPTを作成してOAuthを選ぶまで分からないんです(2往復必須)。

  1. 手順3で表示された固有のCallback URLをコピー
  2. GitHub → OAuth Appの設定画面
  3. Authorization callback URLを貼り替えて保存

5. 認証の確認

設定が完了したら動作確認です。

  1. 作成したGPTで「pppp606/hogeのIssue一覧を見せて」と入力
  2. GitHub認可画面が出るので承認
  3. 正常にリストが返ってきたら設定成功!
  4. 公開設定(共有するボタン)を必ず 自分だけ に変更

実際の使い方

議論の後に:

今の内容をpppp606/hogeにIssueとして作成して

既存Issueの更新:

pppp606/hogeに立っている最新のissueを読んで実行計画を追記して

活用アイデア

Issueを作成する目的を伝える

自分は各リポジトリに .claude/commands/issue-task-run.md というコマンドを用意しているので、GPTの指示に以下のプロンプトを追加しています

プロジェクト内に.claude/commands/issue-task-run.mdが存在する場合:
- 作成するIssueはこのプロンプトから参照されることを前提にする
- Issueの本文は、issue-task-run.mdが扱いやすい形式にまとめる

これで、ClaudeCodeさんが /issue-task-run コマンドでissueを読みにいって処理を実行します

アイデア用リポジトリの活用

リポジトリに依存しない(そこまで達していない)アイデアを投げ込むための専用リポジトリを用意しています。「こんなライブラリがあったら便利だな」といった内容をとりあえずIssueにして残しておくためのリポジトリです。


結果的に使わないとしても、どんどんIssueに保存しておくとよさそうです。AIさんたちとの会話が積み重なって、いつか大きなプロダクトに繋がるかもしれませんし。

何より、コピペの手間から解放されたのが嬉しいです。最近の若者(AI)は優秀だから、どんどん頼っていきたいという話でした。

Discussion