こんにちは、白々さじきです。
飲み会の日程調整ツールをCloudflare Workers + Hono + D1で作るこの検証プロジェクト。今回はDB分離に続く運用インフラ整備として「古いイベントを毎月自動削除するGitHub Actions」をClaude Codeに丸投げした話です。
今回はもう一つ検証したいことがありました。GitHubイシューをそのままプロンプトとして渡せるかという点です。
GitHubイシューをプロンプトとして渡してみた
これまでの実装では、やりたいことをテキストで直接Claude Codeに渡していました。今回は「GitHubイシューのURLを渡すだけで実装してもらえるか」を試してみました。
実際に渡したプロンプトはこれだけです。
下記のIssueを実行してください。

GitHub Actionsで月次データクリーンアップを実装する · Issue #2 · sirajirasajiki/guided-schedulerやること GitHub Actionsで月次データクリーンアップを実装する 詳細・背景 候補日の最大日から30日以上経過したイベントを毎月1日3時(JST)に自動削除する。 削除対象は本番DB(guided-scheduler, --env...
イシューの中身はこうです。
タイトル: GitHub Actionsで月次データクリーンアップを実装する
- 候補日の最大日から30日以上経過したイベントを毎月1日3時(JST)に自動削除する
- 対象は本番DB(guided-scheduler, --env production)のみ
- workflow_dispatch で手動実行も可能にする
- 削除件数をログに出力する
完了条件:
- .github/workflows/cleanup-old-events.yml が作成されている
- cronスケジュール: 毎月1日 2:00 JST
- 子テーブルのレコードを先に削除する(外部キー制約を考慮)
- GitHub Secrets: CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID を設定する
結果として、Claude
CodeはイシューのURLからページを取得し、要件を読み取ったうえで実装まで完了しました。.github/workflows/cleanup-old-events.yml
の作成、SQL設計、外部キー制約を考慮した削除順序の決定、すべてイシューの内容から判断しています。
「GitHubイシューをそのまま渡す」は普通に機能する。 要件が整理されていれば、URL1行で実装まで完結します。逆に言うと、イシューの書き方が実装品質に直結します。今回のイシューには完了条件まで明記していたので、手戻りなく一発で実装が完了しました。
教訓:Claude Codeへの指示はテキストでなくてもよい。整理されたGitHubイシューのURLを渡すだけで実装できる。発注スキルの本質は「何を書くか」であって「どこに書くか」ではない。
なぜ月次クリーンアップが必要か
このツールには認証機能がありません。URLを知っている人なら誰でもイベントを作れます。放置すると古いデータが積み上がり続けるため、「候補日の最終日から30日以上経過したイベントを毎月1日に自動削除する」仕組みが欲しくなりました。
手動で定期的に削除するのは運用として続かないので、GitHub Actionsのcronを使って自動化することにしました。
作ったもの
.github/workflows/cleanup-old-events.yml
を作成し、以下を実現しました。
- 毎月1日 18:00 UTC(≒ 2日 3:00 JST)に自動実行
workflow_dispatchで手動実行も可能dry_run: trueオプションで削除せず対象件数だけ確認できる-
削除順序は
participants→events(外部キー制約を考慮)
削除対象の判定はSQLiteの json_each を使って
candidate_dates(JSON配列)の最大値を取り出しています。
SELECT e.id FROM events e, json_each(e.candidate_dates) AS dates
GROUP BY e.id
HAVING MAX(dates.value) < date('now', '-30 days')
DBへのアクセスは
wrangler d1 execute guided-scheduler --remote 経由です。GitHub
ActionsのランナーからCloudflare D1に直接SQLを投げる構成になっています。
dry_runオプションの設計
「本番DBに対して動かす前に確認したい」という需要は確実にあります。そこでワークフローに
dry_run 入力を追加しました。
workflow_dispatch:
inputs:
dry_run:
description: 'dry_run: true にすると削除せず対象件数の確認のみ行う'
type: choice
options:
- 'false'
- 'true'
dry_run: true のときは「Count deletion
targets」ステップで対象イベントの一覧を出力し、削除ステップをスキップします。本番実行前にまずこれで確認する運用にしています。

GitHub Secretsの設定
ワークフローからCloudflareにアクセスするために2つのSecretが必要です。
CLOUDFLARE_API_TOKEN:Cloudflare APIトークンCLOUDFLARE_ACCOUNT_ID:CloudflareのアカウントID
設定場所:GitHub リポジトリ → Settings → Secrets and variables → Actions → Secretsタブ → New repository secret

| Secrets | Variables | |
|---|---|---|
| ログへの表示 | *** でマスクされる |
そのまま表示される |
| 設定後の値 | 読み返し不可 | 読み返し可能 |
| 用途 | APIトークン等の機密情報 | 環境名、フラグ等 |
APIトークンは機密情報なのでSecrets一択です。
Cloudflare APIトークンの作成
Cloudflare ダッシュボード → My Profile → API Tokens → Create Token
テンプレートは使わず Create Custom Token を選びます。



必要な権限は以下の2つです。
| スコープ | リソース | 権限 |
|---|---|---|
| Account | D1 | Edit |
| User | User Details | Read |


Account Resources は「自分のアカウントのみ」に絞るとより安全です。
つまずいたこと3連発
① APIトークンの権限が足りなかった
最初は
Account > D1 > Edit
だけ設定して実行したところ、こんなエラーが出ました。
Authentication error [code: 10000]
Getting User settings...
Unable to retrieve email for this user.
Are you missing the `User->User Details->Read` permission?
User > User Details > Read
が不足していました。エラーメッセージにそのまま書いてあるのに最初は見落としていました。
教訓:wranglerのエラーメッセージはちゃんと読む。権限不足の場合は不足している権限名まで書いてある。
② CLOUDFLARE_ACCOUNT_IDに間違った値を入れた
権限を追加してもまだ同じエラーが出続けました。原因を探ると
CLOUDFLARE_ACCOUNT_ID に間違った値が入っていました。
トークン作成後に出てきたCurlコマンドを実行しその時に出てきたIDの値がアカウントIDと勘違いしていました。
| 値 | 取得場所 |
|---|---|
CLOUDFLARE_API_TOKEN |
My Profile → API Tokens → トークン作成後の画面 |
CLOUDFLARE_ACCOUNT_ID |
Workers & Pages の右サイドバー |

教訓:CLOUDFLARE_ACCOUNT_IDはトークン画面ではなくWorkers & Pagesのサイドバーから取得する。画面に複数のIDが出てくるので混乱しやすい。
③ Node.js 20のDeprecationワーニング
Actions実行時にこんなワーニングが出ました。
Node.js 20 actions are deprecated.
actions/checkout@v4, actions/setup-node@v4, pnpm/action-setup@v4 are running on Node.js 20.
ワークフローのenvに
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
を追加し、setup-nodeのバージョンも
'24'
に変更して対応しました。実行後は以下のワーニングが残りますが、動作への影響はありません。
Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24.
「アクションのコード自体は20向けだが、強制的に24で実行している」という情報提供です。各アクションの管理者がNode.js 24対応バージョンをリリースするまでは消せません。
教訓:FORCE_JAVASCRIPT_ACTIONS_TO_NODE24で対応できるが、残るワーニングはaction側の対応待ち。動作には問題ない。
ローカルでのDB確認コマンド
DB確認用のコマンドも整備しました。
pnpm db:events:dev # 開発DB のイベント一覧
pnpm db:events:prod # 本番DB のイベント一覧
当初
--local
フラグでローカルSQLiteを直接叩くコマンドを作ったのですが、Windowsではwranglerが内部で起動するworkerdプロセスとlibuv(Node.jsの非同期I/Oライブラリ)が衝突してクラッシュしました。
Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 76
ELIFECYCLE Command failed with exit code 3221226505.
開発DBもCloudflareのリモートD1として運用しているため、--remote
でリモートに接続する方式に変更しました。
教訓:Windows環境でwrangler d1 execute –localは動かない場合がある。開発DBもリモートD1として持つ構成にすると –remote で統一できて問題が起きない。
GitHub ActionsのCronは次回実行が見えない
設定後に「次回いつ動くか確認したい」と思ったのですが、GitHub Actionsには次回実行予定時刻を表示する機能がありません。Cron式から手動で計算するしかないです。
今回の 0 18 1 * * なら次回は6月1日 18:00 UTC(= 6月2日 3:00
JST)です。
もう一つ注意点があります。リポジトリへのpushが60日間ないとcronが自動停止します。停止するとメール通知が届くので、その場合はActionsタブから手動で有効化し直してください。
学びと全体の感想
今回は実装自体よりも Cloudflare側の設定とGitHub Secretsの設定でつまずいた 印象が強いです。コードはClaudeが書いてくれますが、外部サービスの認証・権限周りは人間が理解して設定する必要があります。
特に「どの画面のどのIDを使えばいいか」は、慣れていないと迷います。Cloudflareの場合、アカウントID・データベースID・APIトークンIDがそれぞれ別の画面に存在していて、初見では混乱しやすいです。
完全委任で詰まるのはコードではなく、外部サービスとの接続部分が多い。これはClaudeに丸投げできない領域なので、あらかじめ自分で理解しておく必要があります。
次にやること
- 6月1日の自動実行を確認する
サポートのお願い
下記リンクからお買い物いただけると、ブログ運営のための費用が増え、有料サービスを利用した記事作成が可能になります。ご協力よろしくお願いします!

コメント