GASプロジェクトが数十個ある。
6年間、業務のたびにGASを書いてきた結果がこれだ。請求書発行、チャット通知、スプレッドシート集計、フォーム連携。便利なのは間違いない。だが数十個もあると、どこかが壊れる。
APIの仕様変更、スプレッドシートの列追加、認証トークンの期限切れ。壊れるたびにエラーメールが飛んでくる。GASの実行ログを開いて、スタックトレースを読んで、直す。直したら再実行して確認する。
月に2〜3回はこの作業をやっている。1回あたり30分。先月は金曜の夜にエラーメールが飛んできて、週明けの打ち合わせで使うスクリプトだったから夜中に直すハメになった。地味だが確実に生活を侵食する。
「壊れたことを検知して、修復コードを提案してくれる仕組みがあれば——」
その発想から、n8n + Gemini APIで「GASの自律修復パイプライン」を組んだ。GASのエラーをチャットツールに構造化通知し、n8n経由でGemini APIに修復コードを生成させて、提案として返す仕組みだ。現状は安全重視で「提案まで自動、適用は人間」の半自動(Human-in-the-loop)にしている。
自律修復パイプラインの全体像
[GAS実行エラー発生]
↓
[GAS: try-catch → チャット通知]
エラーメッセージ・関数名・スタックトレースを構造化して投稿
↓
[n8n Webhook受信]
チャットメッセージをパースしてエラー情報を抽出
↓
[Gemini API呼び出し]
エラー内容を入力 → 修復コードを生成
↓
[修復コードをチャットに返信]
チームが確認 → 問題なければ適用
ポイントは**最後が「自動適用」ではなく「人間の確認」**になっていること。GASのコードを勝手に書き換えるのは怖すぎる。AIが提案して、人間がレビューして適用する。完全自律修復はもう少し精度が上がってから考える。
通知先はうちの場合Chatworkだが、SlackでもDiscordでもTeamsでも構造は同じだ。Webhookを受け取れるチャットツールなら何でもいい。
GAS側:エラーを構造化してチャットに投げる
GASのtry-catchでエラーを拾い、チャットツールに投げる。ただし「エラーが起きました」だけでは修復のしようがない。LLMに渡せる粒度で構造化するのがミソ。
function withErrorNotification(fn, scriptName) {
return function() {
try {
return fn.apply(this, arguments);
} catch (e) {
const errorInfo = {
script: scriptName,
function: fn.name,
message: e.message,
stack: e.stack,
timestamp: new Date().toISOString(),
};
const body = [
'[GAS Error]',
`Script: ${errorInfo.script}`,
`Function: ${errorInfo.function}`,
`Error: ${errorInfo.message}`,
`Stack: ${errorInfo.stack}`,
`Time: ${errorInfo.timestamp}`,
].join('\n');
// チャットツールに通知(自前のHTTP送信関数)
// Chatwork: POST /v2/rooms/{id}/messages
// Slack: POST to Webhook URL
// いずれも UrlFetchApp.fetch() で叩ける
notifyToChat(body);
throw e; // エラーは再throwして、GASのログにも残す
}
};
}
使い方はシンプル。既存の関数をラップするだけ。
// Before
function processMonthlyInvoice() { /* ... */ }
// After
const processMonthlyInvoice = withErrorNotification(function processMonthlyInvoice() {
/* 既存の処理をそのまま */
}, 'invoice-automation');
数十個全部に入れるのは大変なので、まずは壊れやすいやつ(外部API連携系)から入れた。
n8n側:Webhook → Gemini → チャット返信
VPS上のn8nで3つのノードを繋ぐ。チャットツール側でWebhookの設定が必要になる(Chatworkならメッセージ作成イベント、SlackならEvent Subscriptions)。
ノード1:Webhook受信とメッセージパース
チャットのメッセージを受け取るWebhookノード。[GAS Error] を含むメッセージだけをフィルタする。
エラー通知は Key: Value 形式で構造化しているので、フィールド単位でsplitして抽出する。
// n8n Codeノード:エラーメッセージのパース
const body = $input.first().json.body;
const lines = body.split('\n');
const fields = {};
for (const line of lines) {
const colonIdx = line.indexOf(':');
if (colonIdx > 0) {
const key = line.substring(0, colonIdx).trim();
const val = line.substring(colonIdx + 1).trim();
fields[key] = val;
}
}
// Stackは複数行にまたがることがあるので、
// Stack行以降を全て結合する
const stackStart = body.indexOf('Stack:');
const timeStart = body.indexOf('Time:');
if (stackStart > -1 && timeStart > -1) {
fields['Stack'] = body.substring(stackStart + 6, timeStart).trim();
}
return [{
json: {
script: fields['Script'] || '',
function: fields['Function'] || '',
error: fields['Error'] || '',
stack: fields['Stack'] || '',
}
}];
ノード2:Gemini API呼び出し
HTTP Requestノードで Gemini API を叩く。プロンプトの設計が肝だ。
あなたはGoogle Apps Scriptの修復エキスパートです。
以下のGASエラーを分析し、修復方法を提案してください。
## エラー情報
- スクリプト名: {{script}}
- 関数名: {{function}}
- エラーメッセージ: {{error}}
- スタックトレース: {{stack}}
## 回答形式
1. **原因分析**(1〜2文で簡潔に)
2. **修復コード**(修正が必要な部分だけをコードブロックで)
3. **再発防止策**(あれば1文で)
コードは完全に動作するものを書いてください。
推測で補完する場合は「推測」と明記してください。
「推測と明記」を入れているのが大事。エラーメッセージだけではコード全体が見えないから、Geminiが勝手に補完する部分が出てくる。それを明示させることで、人間がレビューするときの判断材料になる。
ノード3:チャット返信
Geminiの出力をそのままチャットに投稿する。Chatworkなら [info] タグで囲むと情報ボックスになって、修復提案がパッと目に入る。
Geminiの修復精度、正直なところ
テスト時に7件のエラーを流してみた結果がこれ。
そのまま使えた(4件):
-
TypeError: Cannot read properties of undefined→ 変数のnullチェック追加を提案。そのまま適用して解決 -
Exception: Service invoked too many times→Utilities.sleep()の挿入を提案。妥当 -
Exception: Request failed for https://...→ リトライ処理の追加を提案。実用的 -
ReferenceError: xxx is not defined→ typoの指摘。正確
方向性は合ってたが修正が必要(2件):
- スプレッドシートの列ズレ → 「列番号を確認してください」と返してきた。方向は合ってるがコード全体が見えないので具体的な修正は出せない
- 外部APIのレスポンス変更 → 古い仕様に基づいた修正を提案。新仕様は知らないので仕方ない
使えなかった(1件):
- OAuth2トークン期限切れ → 「再認証してください」。そりゃそうだ。コードの問題じゃない
体感で、5〜6割はそのまま使える修復案が返ってくる。スタックトレースを読んで「ああ、この行で落ちてるのか」と把握するまでが一番時間かかるんだけど、そこをGeminiが代行してくれる。
実際に飛んでくるメッセージのイメージ
エラー発生から修復提案まで、チャットにはこんな感じで流れてくる。
[GAS Error]
Script: invoice-automation
Function: processMonthlyInvoice
Error: TypeError: Cannot read properties of undefined (reading 'getContentText')
Stack: at processMonthlyInvoice (Code:42)
Time: 2026-03-14T22:15:03.000Z
↓ 数秒後、Geminiの修復提案が返ってくる
🔧 Gemini修復提案
1. 原因分析
UrlFetchApp.fetch() のレスポンスが null の可能性がある。
ネットワークエラーまたはタイムアウト時に発生する。
2. 修復コード
const response = UrlFetchApp.fetch(url, options);
if (!response) {
throw new Error('API応答なし: ' + url);
}
const text = response.getContentText();
3. 再発防止策
UrlFetchApp.fetch() の戻り値チェックを共通化し、
全API呼び出しに適用する。
これが自動で飛んでくる。あとは人間が「この修正で合ってるな」と確認して適用するだけ。
Phase 2:コード本体もGeminiに渡す
今の構成では、Geminiにはエラー情報しか渡していない。修復対象のコード本体は渡していない。
Apps Script APIでスクリプトの中身を取得して、エラー情報と一緒にGeminiに渡せば精度は上がる。構成はこうなる。
[GAS Error通知]
↓
[n8n: Apps Script APIでコード取得]
↓
[Gemini API: エラー情報 + コード本体を入力]
↓
[修復コード提案]
これは計画中。Apps Script APIでプロジェクトのソースを取得する部分の実装がまだ終わっていない。コード全体を渡せれば、スプレッドシートの列ズレ系のエラーも正確に直せるようになるはず。
なぜClaude APIじゃなくてGemini?
正直に言うと、コスト。
このパイプラインはエラーが起きるたびに動く。月に2〜3回とはいえ、1回あたりのプロンプトがそこそこ長い(エラー情報 + プロンプト指示で2000トークン前後)。Gemini APIは無料枠が大きいので、この用途には向いている。
修復精度だけで言えばClaude Sonnetの方が上だと感じている。特にGASのコード補完はClaudeが強い。同じエラーを両方に投げてみたことがあるが、Claudeの方がコードの文脈を読んだ修正案を出してきた。Geminiは「一般的にはこう直す」という提案が多い。
だが「月に数回しか動かないパイプラインに課金するか」という判断で、まずGeminiで組んだ。精度が足りなければモデルを差し替えればいい。n8nのHTTP Requestノードのエンドポイントを変えるだけだ。ここがn8nで組む旨みでもある。
n8nで組む利点
「GASの中でGemini APIを叩けばいいのでは」と思うかもしれない。実際できる。だがn8nで組む理由が3つある。
-
非同期で分離できる。GAS内でLLM APIの応答を待つと、GASの実行時間制限(6分/回)を圧迫する。エラー通知だけGASから投げて、修復分析はn8n側で非同期に走らせれば、GAS側はすぐに処理を終えられる。また、構文エラーやタイムアウトでGASが完全に停止したケースでも、GASの実行ログ(メール通知)をn8nのメールトリガーで拾う拡張ができる
-
24時間稼働。n8nはVPS上で常時動いている。GASのトリガーは最短1分間隔だが、実行時間制限がある。長時間の修復分析には向かない
-
他のパイプラインと統合できる。うちのn8nではニュース収集、SNSトレンド監視、チャットメッセージ吸い上げなど、十数本のワークフローが動いている。GAS修復もその1本として管理できる。Webhookの設定、エラー時の再実行、ログの一元管理——全部n8nの管理画面で完結する
構築コスト
3時間で組めた。GAS側のエラーラッパー関数(30分)、n8nのワークフロー構築(1時間)、Geminiのプロンプト調整(1時間)、テスト(30分)。
| 項目 | コスト |
|---|---|
| n8n(VPS) | 既存環境を流用(追加費用なし) |
| Gemini API | 無料枠内 |
| 開発時間 | 約3時間 |
月に2〜3回 × 30分 = 月1〜1.5時間の削減。2〜3ヶ月で元が取れる計算。
数十個のGASプロジェクトを面倒見るのは地味に大変だ。全部を完璧に保守するのは無理。壊れることを前提に、「壊れたら自動で直す候補を出してくれる仕組み」を先に作っておく方が現実的だった。
GASに限らず、「保守対象が多すぎて全部見きれない」状況はどこにでもある。Lambda、Cloud Functions、cron job——動いているスクリプトが増えるほど、壊れたときの対処コストも増える。LLMにエラー情報を食わせて修復案を出させるパターンは、GAS以外にも使える。
壊れないシステムを作るより、壊れても戻れるシステムを作る方が、長く生き残る。
関連記事:
- 「Chatworkに確定連絡が来たら請求書を送る」をGASで自動化する — GAS×API連携の実装パターン