GitHubのWebhookを使い、GitHub Projects上のIssueをProjectに設定されたフィールドの日付でGoogle カレンダーにタスク登録する(Google ToDoにTaskとして登録する)ツールを作りました。

ソースコードはこちら。
動機
これまで個人開発用のタスク管理でいくつかのタスク管理ツールを使ってきましたが、何を使ってもしばらくしたら見なくなってタスクが放置されてしまっていました・・・
定期的にチェックすれば済む話ではありますが、あくまで趣味開発なのでそこまで頑張ってチェックしなくてもタスクが目に入ってくるようにしたいと思い、今回Google ToDoに連携できるようにしました。
自分は普段、日々の細々としたタスクはGoogle カレンダーにタスク登録しています。
基本的に全ての予定をGoogle カレンダーで管理しているので予定を確認する時にタスクも目に入ってくるようになっているのと、それでいて完了したらカレンダー上に表示されないようにできて終わったタスクを忘れられるので。
あと、AndroidのGoogle カレンダーアプリのウィジェットが好みなんですよね。
ホーム画面上で予定を確認できるだけでなくウィジェット内でスクロールできて、カレンダーを開かなくてもちょっと先の予定まで確認できるのが好きなポイントです。
ただ、Google ToDoは簡潔なタスクを登録しておくにはちょうど良いと思っているのですが説明や作業メモなど色々書き込んでいきたいタスクには向いていない感覚で、開発系のタスクは専用のタスク管理ツールで管理しつつ、Google カレンダー上でも確認したいと考えました。
そこで既存のタスク管理サービスを調べてみたのですが、無料プランではカレンダー連携できなかったり、連携できてもGoogle カレンダーのタスクではなく予定として登録されたりして自分の使い方に合うサービスが見つからなかったのでGitHub IssueとGoogle ToDoの連携を自作することにしました。
要件
今回の要件は以下としました。
- IssueにProjectの日付フィールドを設定したらその日付でタスク登録されて欲しい
- タスクに登録する情報は以下
- Issueタイトル
- Issue URL
- Issue 対応期限
Issue自体は期限日の情報を持てないのでProjectに追加し、Projectの日付フィールドをIssueの期限としてタスク登録する

日付の変更は反映されて欲しい
- IssueをCloseしたらタスクも完了されて欲しい
- タスクに登録する情報は以下
- 連携するのはGitHub → Google ToDoの一方向
- カレンダー側で日付変更・完了はできなくて良い
構成
本番環境はこんな構成で動いています。
- 実装
- Hono
- 実行環境
- Cloudflare Workers
- データの保存先
- Cloudflare D1
- 管理画面のアクセス制御
開発
実は当初、「GitHub Actionsで日付の設定を検知してGoogle Cloudのサービスアカウントを使ってタスク登録できれば」位のシンプルな構成を想定していました。
ただ、色々ネックとなるポイントがあり、最終的には想定よりややこしい(めんどい)システム構成になりました。
問題その1: GitHub Actionsが使えない
さてワークフローを組んでみようとGitHub Actionsのトリガー一覧を見て気づいたのですが、Project系のトリガーはありません。
また、IssueならトリガーはありますがIssueに設定されたProjectのフィールドを更新してもIssue更新のトリガーは発火しませんでした。
そこで調べてみて、Webhookのproject_v2_itemイベントなら目的を満たせそうだと分かりました。
(ちなみに、このドキュメントにはpublic previewとなっているProjects系Webhookへのフィードバックリンクが貼ってあり、そこにGitHub Actions対応の要望への返信として「Projects are Org level and Actions are Repo level.」と記載がありました。まあそうだよね、って感じではあるので当面GitHub ActionsでProjectsは扱えなさそうです。リポジトリ単位のProjectsだけでも使えると助かるのですが・・・)
元々GitHub Actionsを使って(自分側では実行環境を管理せずに)連携しようと考えていたのでWebhook面倒だなと思ったのですが、気になっていたHonoを試す良い機会とWebhook用のAPIサーバーを実装してみる事にしました。
GitHubのProjectsリポジトリ単位や個人アカウント単位でも作成できますが、projects_v2_itemのWebhookを使えるのはOrganization単位のProjectです。
そのため、自分1人の個人Organizationが爆誕しました。
(「タスク管理するだけのためになんでOrganization作ってるんだろう」と思ったりも・・・w)
GitHubのWebhookを受け取るサーバーを実装するのは初めてだったのですが、イベント毎のペイロード以外にも「secretを使ってリクエスト元を検証しよう」などベストプラクティスがドキュメントにまとまっています。
このドキュメントを参考にして「project_v2_itemイベント特有ではないsecretの検証はMiddlewareに持たせよう」など考えながら実装を進めました。
問題その2: WebhookのペイロードにIssue情報が含まれていない
要件の所にも書きましたがタスク登録する際は
- Issueタイトル
- Issue URL
- Issue 対応期限
を持たせたいと考えていました。
しかし、projects_v2_itemイベントではProject上のアイテム情報は取れますが、そのアイテムがIssueだったとしてもIssue情報は取れません。
そこで、ペイロードに含まれているid (item_id)を使ってGET orgs/{org}/projectsV2/{project_number}/items/{item_id}のAPIを叩くことでIssueの情報を取得する事にしました。
GitHubのAPIを叩くには当たり前ですがトークンが必要です。
ただ、GitHub Actionsとは違いWebhookではWebhookを受け取るサーバー自体はGitHubへのアクセス権を持っていません。あくまでGitHubから送信されてくる値を受け取るだけです。
そのため、GitHub Appを使って権限を付与しました。
- GitHub Appを作成
- それをOrganizationにインストール
- WebhookサーバーでGitHub Appの秘密鍵などを使い、Organizationにアクセスするための短期間のトークンを発行
とすることでProject Item取得のAPIを叩けるようにしています。
問題その3: サービスアカウントではタスク登録できない
Googleのリソースにプログラムからアクセスする方法はいくつかありますが、最初はサービスアカウントを作ってその権限でタスクリストにタスクを登録しようと考えていました。
例えばGoogle ドキュメントであればドキュメントに対してサービスアカウントの閲覧/編集権限を付与し、そのサービスアカウントとして認証することで比較的簡単にアクセスできるようになります。
しかしGoogle ToDoのタスク・タスクリストはそのタスクリストが作成されているGoogleアカウントの権限でしかアクセスできません。
(タスクには他ユーザーに共有する機能が無いのでそれ自体は納得です)
そこでOAuthを使って必要なアカウントの権限を取得する必要が発生しました。
この辺りでだいぶ面倒になってきていたのですが、Claude Codeがやってくれました。
こういう、「本来やりたい事のメインではないが必要」な事をやってくれるのは有り難かったです。
「絶対この機能が欲しい!」ではなく「あったら良いなあ・ちょっと実装を試してみるか」位の気持ちだったので自分の力だけで実装していたらめんどくなってモチベが尽きていたような気がします・・・
ともあれ、こうしてGoogle ToDoにタスク登録できるようになりました。
注意点として、API経由で登録する場合タスクの時間は指定できません。
- https://developers.google.com/workspace/tasks/reference/rest/v1/tasks/insert?hl=ja
- https://developers.google.com/workspace/tasks/reference/rest/v1/tasks?hl=ja
due
タスクの予定日(RFC 3339 タイムスタンプ)
(省略)
タイムスタンプの時刻部分は破棄されます。API を使用してタスクのスケジュール設定時刻を読み書きすることはできません。
そもそもGitHubのProjectのフィールドに設定できるのは日付だけなので今回のケースでは特に問題になりませんでしたが、API経由でタスクを登録される際はご注意ください。
データの保存や管理画面へのアクセス制限
そんなこんなで実装できたのですが、いくつかトピックを。
今回の実装に当たってIssueとTaskの紐づけ情報などデータをどこかしらに保存しておく必要があったのですが、Cloudflare D1を使ってみました。
Honoやwranglerコマンドからシームレスに使えて体験が良かったです。
また、今回は自分1人だけが使う想定で実装したのでログイン機能はつけていません。
しかしWebhookのリクエストやOAuthのcallbackを受け取るために環境はインターネット上に公開する必要があり、管理画面ではタスクを登録するタスクリストを選べるようにしています。つまりそのままではタスクリストが全世界に公開されてしまいます。
そこで、前に試したことがあったCloudflare Accessを使って管理画面のURL全体にアクセス制限を掛けました。
管理画面用のURLにアクセスしたらGitHubアカウントでのログインを要求し、ログイン後特定のアドレスで無かった場合はアクセスを拒否するようにしています。

CloudflareはZero Trustを実現する様々なサービスを提供しています。
Cloudflare Accessはその一部で、Cloudflare Oneで設定したIdPを使ってアクセス制御を設定できます。
対応しているIdPの中にはGitHubもあり、GitHubで設定したOAuthアプリと連携することで
- GitHubログインした状態で
- ログイン情報が特定の条件に一致した時だけアクセスを許可する
といったアクセスポリシーを設定できるようになります。


おかげで、実装いらずでアクセス制御を掛けられました。
開発ツール
今回使った・試してみた開発ツールについても紹介します。
- Cloudflare Tunnel
- 1Password Environments
- mise
- pnpm
まず、Cloudflare Tunnel。
別のプロジェクトの開発でも使っている推しサービスです。
Cloudflareで管理しているドメインのサブドメインを開発環境に割り当ててインターネット上に公開でき、Webhookを開発環境で受けられてとても便利でした。
そして、環境変数の管理ではローカルPC上のファイルに直に環境変数を書くのではなく1Passwordを使ってより安全に管理したいなと思っていたので1Password Environmentsを試してみました。
1Password CLI自体は元々設定済みで、1Password上で環境変数を登録してマウント先のファイルパスを設定するだけで簡単に使えました。

Node.jsなどのバージョン管理にはこれまでasdfを使っていたのですが、いつまで経ってもコマンドを覚えられていなかったのでこの機会にmiseに移行してみました。
また、折角なので色々試してみよう、位のノリでpnpmも使ってみました。
まだnpmのコマンドがそのままpnpmに変わった位の使い方しかしていませんが・・・
感想
と、欲しいものを作りがてら実験もしてみました。
色々試せて楽しかったです。
ツール自体は
- Actionsでやるぞ → Webhookじゃないと駄目
- サービスアカウント使おう → OAuthが必要
- WebhookからIssue情報を取れない → APIからIssue情報を取得
と予定から結構ずれて想定より大掛かりなツールになってしまったな・・・と思っています。
想定が狂うたびに面倒な気持ちにもなりましたが、Claude Codeのお陰で作りきれました。
有り難い。
その一方、AIがあると良くも悪くも作りきれてしまうな、とも感じました。
自分の手で開発していたら想定が狂った段階で一度立ち止まってやり方を見直しているかもなー、とうっすら考えながらそのまま開発を進めていました。
途中からは半ば実装し切ることが目的になっていたのもありますけど。
今回は実験も兼ねて動く状態まで作りきってしまうぞ、と。
最終的には満足できるものが作れてよかったです。
以上、長い文章になってしまいましたがお読み頂きありがとうございました。