GitHub Actions cronが一度も動いていなかった:Node20×Supabase WebSocket不可とworking-directory罠

ローカルで動くのに cron が「一度も走っていない」
gh run list --workflow=collect.yml
# → (0件)
自動収集パイプラインをリリースして数日後、確認のために叩いたコマンドが空を返しました。npm run collect はローカルで毎回 DB を更新していたので「動いてる」と思い込んでいたのです。cron は一度も成功していませんでした。
個人開発の localbiz-event-finder(商工会議所・connpassのイベントを自動収集するツール)で同じ問題を踏みました。ローカル実行では DB が更新されるため、GitHub Actions 側の停止に数日間気づかず。修正後は 1 分 15 秒で完走し、126 件登録・score≥60 のイベントで Slack 通知が発火しました。masatoman.net の記事用データ収集も自動化に依存しているため、パイプラインの死活監視は最優先で確認すべき事項です。
この記事でわかること:
working-directoryの誤指定が cron を silent に止める仕組み- Node.js 20 ×
@supabase/supabase-jsRealtime でなぜ WebSocket が落ちるのか - 今日試せる確認コマンド 3 つ
原因1: working-directory がネストしたサブディレクトリを指していた
# ❌ 問題のある設定
jobs:
collect:
runs-on: ubuntu-latest
defaults:
run:
working-directory: localbiz-event-finder # ← これが罠
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run collect
リポジトリのルート自体が localbiz-event-finder/ でした。つまり working-directory: localbiz-event-finder は、存在しない localbiz-event-finder/localbiz-event-finder/ を探しにいきます。
期待: /home/runner/work/localbiz-event-finder/localbiz-event-finder/
実際: /home/runner/work/localbiz-event-finder/localbiz-event-finder/localbiz-event-finder/
^^^^^^^^^^^^^^^^^^^
存在しないディレクトリ
GitHub Actions はこの設定ミスを push 時に検証しません。ジョブが実行されてはじめて Error: ENOENT: no such file or directory, chdir が出ます。かつ、cron トリガーではこのエラーが Slack 等に飛ばない限り気づけません。
修正
# ✅ 修正後(working-directory ごと削除)
jobs:
collect:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run collect
リポジトリがモノレポの場合は working-directory: packages/foo のように相対パスを起点にするか、cd を明示するほうが意図が明確です。
原因2: Node.js 20 × Supabase Realtime で UND_ERR_CONNECT_TIMEOUT
working-directory を修正しても、もう1つのエラーが出ました。
RealtimeClient error: UND_ERR_CONNECT_TIMEOUT
Error: connect ETIMEDOUT
at RealtimeClient._connect (node_modules/@supabase/realtime-js/...)
@supabase/supabase-js の RealtimeClient は、Node.js 環境でネイティブ WebSocket(global.WebSocket)を要求します。Node.js 20 にはネイティブ WebSocket が実装されていません(実験的フラグが必要)。そのためタイムアウトで落ち続けます。
| Node.js バージョン | ネイティブ WebSocket | Supabase Realtime |
|---|---|---|
| 18 | ❌ | ws パッケージで代替可 |
| 20 | ❌(実験的) | UND_ERR_CONNECT_TIMEOUT 発生 |
| 22 | ✅ | ネイティブで動作 |
| 24 LTS | ✅ | ネイティブで動作 |
(Supabase 公式ドキュメントより。Node.js のバージョン別 WebSocket 対応状況)
修正
# ✅ Node.js 22 に上げる
- uses: actions/setup-node@v4
with:
node-version: '22'
これだけで WebSocket 問題は解消します。Realtime を使わず supabase.from(...).insert() だけなら Node 20 でも動きますが、createClient が内部で Realtime 接続を初期化するため影響を受けます。
なぜ「一度も動いていない」に気づかないのか
cron ジョブが失敗しても、以下の設定がないと誰にも通知されません:
- Slack / Discord への通知ステップ
- GitHub の「ワークフロー失敗メール通知」(デフォルトは ON だが迷惑メールに流れやすい)
gh run listの定期確認
ローカル実行が成功していると「動いてるはず」のバイアスが強くなります。特に個人開発で DB が別経路(手動実行)でも更新されている場合、cron の停止を数週間見逃すことがあります。
今すぐ確認できる 3 つのコマンド
# 1. ワークフローが最近走ったか確認
gh run list --workflow=your-workflow.yml --limit=10
# 2. 直近の実行のログを確認
gh run view <RUN_ID> --log
# 3. working-directory が正しいか確認(ローカル実行)
ls -la . # リポジトリルートに package.json があるか確認
gh run list が空、または最終実行が数日前なら cron が止まっています。
で、どう稼ぐ?
自動収集パイプラインが止まると、収益機会も止まる
GitHub Actions の cron が機能していない状態は、目に見えないコスト損失です。
たとえばイベント収集ツールの場合:
- cron が止まる → DB が古くなる → ユーザーに古いデータを表示 → 解約につながる
- cron が動く → 毎日最新データを自動取得 → ユーザーが毎日戻ってくる → 継続率が上がる
個人開発 SaaS の収益を安定させるには「自動化の死活確認」がコードを書くのと同じくらい重要です。
確認コストを下げる Slack 通知の最小実装
# ワークフロー末尾に追加するだけ
- name: Notify success
if: success()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
-H 'Content-type: application/json' \
--data '{"text":"✅ collect completed: ${{ steps.collect.outputs.count }} items"}'
- name: Notify failure
if: failure()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
-H 'Content-type: application/json' \
--data '{"text":"❌ collect FAILED: ${{ github.workflow }} @ ${{ github.sha }}"}'
これを入れておくと失敗が即日わかります。Slack Webhook URL は無料で取得でき、設定は 5 分です。
まとめ:今日やること3つ
gh run listで直近ワークフローの実行履歴を確認する — 空なら即アラート、最終実行が数日前なら要調査- Node.js のバージョンを確認する —
node-version: '22'以上を指定していない場合は変更 - Slack 失敗通知を 1 ステップ追加する — cron の死活確認は人が見るより Slack が先に教えてくれる仕組みにする
Next Step
次に読むならこの導線です
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
【第8回】Claude Code で GitHub Issue から PR まで自動化する実装ガイド
GitHub Issue が立った瞬間に Claude Code が実装し、PR を作成するまで自動化する仕組みを、実コード付きで解説。GitHub Actions + claude-code-action の組み合わせで、Issue 駆動開発を実現します。
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
【第1回】Claude Code Skills 入門 — 自作スキルで開発効率を2倍にする実装ガイド
Claude Code の Skills 機能を自作する手順を、masatoman.net 周辺の自動化を Claude Code で回している立場で実コード付きで解説。1 スキル 15 分の投資で月 10 時間の作業を削減する実装ガイドです。
次の実験記録も追う
Claude Code × 個人開発の実験ログ、失敗、判断変更をまとめて追いたい人向けに、月次でLab Freeを届けます。
masatoman のメルマガ — 毎週月曜の朝に 1 通
masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。
この記事が役に立ったらシェア