Google Apps Script(GAS)で作った仕組みが止まったとき、どこを見て、原因をどう切り分けて、どう直すかをまとめます。例はサンプル料理教室で、前日のリマインドメール、予約一覧からカレンダーへの登録、月末の請求書のPDF作成の3本のGASが動いています。読み終わると、エラー通知のメールを受け取ったときに、実行数の画面で何が起きたかを確かめ、エラーの文言から原因を当て、直してから再開するまでを自分でできます。前提は、GASのエディタを開けることと、どのトリガーが何をしているかを把握していることです。GASそのものの始め方は「Google Apps Script(GAS)とは:できること・始め方」で書いています。
この記事の内容
先に決めること
止まったときに慌てないための備えは、エラーが起きる前に決めておくものです。
1つ目は、誰がエラーに気づくかです。実際に運用していると、トリガーで動くGASのエラー通知メールは、トリガーを作ったアカウントにしか届きません。受付がトリガーを作り、代表は何も知らないまま、という形だと、受付が休んだ週にリマインドが止まっていても誰も気づきません。サンプル料理教室では、トリガーはすべて代表のアカウントで作り、さらにGASの中でエラーをスタッフのChatのスペースに送っています。
2つ目は、どのアカウントでトリガーを持つかです。インストール型のトリガーは作った人のアカウントで動くので、実際に運用していると、そのアカウントを止めた日からトリガーも止まります。退職や異動のある人に持たせないことが、いちばん効く予防です。
3つ目は、止まったときに手で回せる手順があるかです。リマインドが止まった日に、受付が手でメールを送れるか。請求書が作れなかった月に、どこまでできていて何が残っているかがシートで分かるか。GASの側で「どこまで処理したか」をシートに書く作りにしておけば、止まっても続きから手で回せます。
4つ目は、GASのままでよい規模かです。外のサービスを何本も呼ぶ、6分を超える処理が毎日ある、といった仕組みは、GASで続けるより n8n に移したほうが止まりにくくなります。目安は最後の節に書きます。
| 決めること | サンプル料理教室の答え |
|---|---|
| エラーに気づく人 | 代表(通知メール)と、スタッフのChatのスペース |
| トリガーの持ち主 | 代表のアカウントにそろえる |
| 処理の記録 | 各行に「送信日時」「予定ID」「状態」を書く |
| 止まったときの手作業 | リマインドは受付がGmailで送る。請求書は状態が空の行だけ作り直す |
止まったときに見る順番
GASが止まったときは、次の順に見ます。いきなりコードを読み始めると、権限や上限の問題なのにコードを疑って時間を使います。
- エラー通知のメールを開き、どの関数が、いつ、どんなメッセージで止まったかを見る
- Apps Scriptのエディタを開き、左の「実行数」で、その時刻の実行の行を開く
- ログの最後の行のエラーの文言と、行番号を見る
- 下の「よく出るエラー」の表で、文言から原因を当てる
- 権限・上限・外の要因(Googleの障害)なら、コードを直さずにその原因を片付ける
- コードの問題なら直し、エディタから手で1回実行して通るのを見る
- 止まっていた間の分が処理されていないかをシートで確かめ、残りを回す
エラー通知のメール
トリガーで動いた関数がエラーで止まったり、最後まで終わらなかったりしても、画面には何も出ません。代わりに noreply-apps-scripts-notifications@google.com から「Summary of failures for Apps Script」という件名のメールが届きます。本文には、スクリプトが最後まで終わらなかったことと、トリガーを無効にしたり設定し直したりするためのリンクが入ります。
実際に運用していると、このメールはトリガーを作った人の受信トレイにだけ届きます。アドオンが作ったトリガーでは届きません。トリガーを無効にしたり消したりすると、通知も止まります。トリガーが有効なまま変えられるのは、通知を受け取る頻度だけで、トリガーの編集画面で選べます。夜のうちに止まって翌朝まで気づかないと困る仕組みはすぐに受け取る設定、毎日何件か失敗しても困らない仕組みは1日ごとにまとめて受け取る設定にします。
通知のメールは英語の件名で、見慣れないアドレスから届きます。迷惑メールに入れたり、見ずにアーカイブしたりしやすいので、代表のGmailでこの差出人にフィルタを作り、スターと「GASエラー」のラベルを付けておきます。

実行数の画面
エディタの左にある「実行数」を押すと、そのプロジェクトの過去の実行が1回1行で並びます。
| 列 | 見ること |
|---|---|
| 開始時間 | いつ動いたか |
| 期間 | 何秒かかったか。6分近いものは時間切れの手前 |
| ステータス | 「失敗」「タイムアウト」「実行中」など |
| 関数 | どの関数から始まった実行か。途中で呼んだ関数は出ない |
| タイプ | 「時間駆動」「トリガー」「編集者」「ウェブアプリ」「アドオン」「Execution API」のどれがきっかけで動いたか |
上の「フィルタを追加」から「ステータス」を選び、「失敗」と「タイムアウト」に絞ると、問題のある実行だけが残ります。行を押すと、その実行で console.log に書いた内容とエラーの文言が出ます。エディタ上部の「実行ログ」は、開発中に手で動かしたときの確認用で長くは残らないので、トリガーで動いた分はこちらで見ます。「実行中」のまま終わらない実行は、行の右の「︙」から「終了」を選ぶと止められます。
期間の列は、止まる前の兆しを見るのにも使えます。リマインドのGASが毎日20秒だったのが、予約が増えて3分になっていれば、半年後には6分に当たります。月に1回、所要時間の伸びを見ておくと、止まる前に分割に手を付けられます。

よく出るエラー
| エラーの文言(例) | 原因 | 直し方 |
|---|---|---|
| その操作を実行するには承認が必要です。 | 一度も承認していない、新しいサービスを足した、承認が切れた | エディタで関数を手で1回実行し、求められた権限を許可する。だめならトリガーを消して作り直す |
| Exception: 〜を呼び出す権限がありません(You do not have permission to call 〜) | 承認の画面で一部の権限を外した、マニフェストの oauthScopes に要るスコープが無い | もう一度承認して全部許可する。appsscript.json の oauthScopes を見直す |
| 最大実行時間を超えました(Exceeded maximum execution time) | 1回の実行が6分を超えた | 処理を分けて続きから再開する(下の節)。シートの読み書きをまとめる |
| サービスが呼び出された回数が多すぎます: メール | 1日の割り当て(メールの宛先数など)を使い切った | 24時間待つ。送る数を減らすか日を分ける |
| Service invoked too many times in a short time: Calendar. Try Utilities.sleep(1000) between calls. | 短時間にカレンダーなどを呼びすぎた | 呼び出しの間に Utilities.sleep(1000) を入れる |
| Service using too much computer time for one day. | トリガーの合計実行時間の1日の上限に達した | トリガーの頻度を下げる。1分おきのトリガーを見直す |
| There are too many scripts running simultaneously for this Google user account. | 同じアカウントで同時に動くスクリプトが多すぎる | トリガーの時刻をずらす。LockService で重ならないようにする |
| Script invoked too many times per second for this Google user account. | 短時間に何度も起動された | 編集時トリガーの中の処理を軽くし、まとめて処理する |
| サーバー エラーが発生しました。もう一度お試しください。 | Googleの一時的な障害、またはメッセージの無いスクリプトのエラー | 時間を置いて再実行する。Google Workspaceのステータスダッシュボードを見る |
| アクセスが拒否されました: DriveApp | 管理者がドメインでドライブのサードパーティアプリを止めている | 管理者に設定を確かめてもらう |
| 〈URL〉 に対して UrlFetch の呼び出しを行うことは、管理者により許可されていません | 管理者の許可リストに外部のURLが無い | 管理者に許可リストへの追加を頼む |
| Limit exceeded: Email Attachments Per Message. | 1通の添付が上限を超えた | 添付を減らすか、ドライブのリンクにする |
| 無効なメールアドレス: 〜 | 宛先のセルが空、全角、前後に空白 | trim() と形の確認を入れ、おかしい行は飛ばして印を付ける |
| TypeError: Cannot read properties of null (reading ‘getRange’) | シートの名前が変わった、または見つからない | シート名を確かめる。getSheetByName の結果が null なら分かる文言でエラーにする |
| Exception: データの行数が範囲の行数と一致しません | setValues に渡した配列の行数と範囲の行数が違う | getRange(行, 列, 配列.length, 配列[0].length) で範囲を配列から作る |
上半分は、コードではなく権限や上限の問題です。この場合はコードを直しても解決しません。下半分は、シートの変更やデータの揺れでコードが想定外の値を受け取った問題です。実際に運用していていちばん多いのは、受付がシートの名前を「予約一覧」から「予約一覧(2026)」に変えて、翌朝のリマインドが null で止まる、というものです。シート名を変えるときはGASの担当に声をかける、と決めておくと防げます。
割り当てと上限
GASには、1日あたりの割り当てと、1回の実行ごとの上限があります。2026年10月時点のApps Scriptの割り当てのページの値です。
| 項目 | 無料のGoogleアカウント | Google Workspace |
|---|---|---|
| 1回の実行時間 | 6分 | 6分 |
| カスタム関数の実行時間 | 30秒 | 30秒 |
| トリガーの合計実行時間(1日) | 90分 | 6時間 |
| メールの宛先数(1日) | 100人 | 1,500人 |
| メールの宛先数・ドメイン内(1日) | 100人 | 2,000人 |
| 1通の宛先数 | 50人 | 50人 |
| URLFetchの呼び出し(1日) | 20,000回 | 100,000回 |
| URLFetchのレスポンスの大きさ(1回) | 50MB | 50MB |
| カレンダーの予定の作成(1日) | 5,000件 | 10,000件 |
| スプレッドシートの作成(1日) | 250件 | 3,200件 |
| ドキュメントの作成(1日) | 250件 | 1,500件 |
| ファイルの変換(1日) | 2,000件 | 4,000件 |
| 同時実行(1ユーザー) | 30 | 30 |
| トリガーの数(1ユーザー・1スクリプト) | 20 | 20 |
| プロパティの1つの値 | 9KB | 9KB |
| プロパティの合計 | 500KB | 500KB |
割り当ては利用者ごとで、最初に使ってから24時間で戻ります。午前0時に戻るのではありません。試用期間中のGoogle Workspaceはさらに制限があり、有料にして支払いの累計が一定額に達し、しばらく経つまで上がりません。導入したばかりのころに止まる原因の多くはこれです。
1回6分は、Google Workspaceでも無料のアカウントでも同じです。公式のベストプラクティスのページには一部のWorkspaceのアカウントで30分という記述もありますが、割り当ての表は6分なので、6分で設計します。
トリガーの合計実行時間は見落としやすい上限です。1分おきのトリガーで毎回10秒かかる処理は、それだけで1日240分になり、無料のアカウントの90分を超えます。Google Workspaceの6時間でも、ほかのトリガーと合わせると届きます。15分おきで足りる処理を1分おきにしない、というのが最初の対策です。
長い処理を分ける
6分を超える処理は、1回で終わらせようとせず、分けて何回かで終わらせます。考え方は3つです。
読み書きをまとめる
まず、6分に当たる前に処理を速くできないかを見ます。GASが遅くなるいちばんの原因は、シートを1セルずつ読み書きすることです。公式のベストプラクティスでは、セルを1つずつ書いた例が約70秒、まとめて書いた例が約1秒とされています。
// 遅い:1行ずつ読み書きする
for (let r = 2; r <= last; r++) {
const v = sheet.getRange(r, 2).getValue();
sheet.getRange(r, 9).setValue(v * 1.1);
}
// 速い:まとめて読み、配列で計算して、まとめて書く
const values = sheet.getRange(2, 2, last - 1, 1).getValues();
const out = values.map(function (row) { return [row[0] * 1.1]; });
sheet.getRange(2, 9, out.length, 1).setValues(out);
ただし、メールの送信のように「1件ごとに済んだ印を付ける」ことが大事な処理は、印だけは1件ごとに書きます。まとめて書くと、途中で止まったときに送った分の印が残らず、二重送信になります。
続きから再開する
処理を分けるときは、どこまで終わったかをどこかに残し、次の実行でそこから始めます。残す場所は、シートの行の「状態」の列か、スクリプトのプロパティです。
const LIMIT_MS = 4.5 * 60 * 1000; // 6分の手前で止める
function processAll() {
const started = Date.now();
const props = PropertiesService.getScriptProperties();
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('会員台帳');
const values = sheet.getDataRange().getValues();
let i = Number(props.getProperty('NEXT_ROW') || 1);
for (; i < values.length; i++) {
if (Date.now() - started > LIMIT_MS) {
props.setProperty('NEXT_ROW', String(i));
scheduleNext();
console.log('続きは ' + (i + 1) + ' 行目から');
return;
}
handleRow(sheet, i, values[i]); // 1行分の処理
}
props.deleteProperty('NEXT_ROW');
clearNext();
console.log('全件終わりました');
}
function scheduleNext() {
clearNext();
ScriptApp.newTrigger('processAll').timeBased().after(60 * 1000).create();
}
function clearNext() {
ScriptApp.getProjectTriggers().forEach(function (t) {
if (t.getHandlerFunction() === 'processAll') ScriptApp.deleteTrigger(t);
});
}
function handleRow(sheet, i, row) {
// ここに1行分の処理を書く
}
4分半で止めているのは、1行分の処理の途中で6分を迎えないための余裕です。止める前に次の行番号をプロパティに書き、1分後に同じ関数を動かすトリガーを作ります。最後まで終わったら、プロパティと続きのトリガーを消します。続きのトリガーを消し忘れると、トリガーの数が20に当たって新しいトリガーが作れなくなるので、作る前に同じ関数のトリガーを消しています。
時間主導型のトリガーで最初から分ける
毎日決まった量を処理する仕組みなら、最初から分けて動かすほうが素直です。サンプル料理教室の請求書なら、会員番号の範囲で「1〜50番」と「51〜100番」に分け、6時台と7時台に別のトリガーで動かします。続きから再開する作りより単純で、どこで止まったかも分かりやすくなります。月末の請求書の作り方そのものは「スプレッドシートで請求書を作り、PDFで送る:GASで月末をまとめる」で扱っています。

自分でエラーを知らせる
通知メールは持ち主にしか届かないので、スタッフにも知らせたいときは、GASの中でエラーを受け取ってChatのスペースに送ります。Google Chatの着信Webhookを使います。
- ブラウザでGoogle Chatを開き、「スタッフ連絡」のスペースに入る
- スペース名の横の矢印を押し、「アプリとインテグレーション」を押す
- 「Webhook を追加」を押し、名前に「GASエラー」と入れて保存する
- Webhookの「その他」→「リンクをコピー」でURLをコピーする

const CHAT_WEBHOOK_URL = 'YOUR_CHAT_WEBHOOK_URL'; // スクリプトのプロパティに置くとより安全
// トリガーからはこの関数を呼ぶ
function sendRemindersSafely() {
try {
sendReminders(); // 本体
} catch (e) {
notifyChat('リマインド送信が止まりました\n' + e.message + '\n' + (e.stack || ''));
throw e; // 通知メールと実行数にも失敗として残す
}
}
function notifyChat(text) {
UrlFetchApp.fetch(CHAT_WEBHOOK_URL, {
method: 'post',
contentType: 'application/json; charset=UTF-8',
payload: JSON.stringify({ text: text }),
muteHttpExceptions: true,
});
}
catch の中で最後に throw e しているのが要です。エラーを受け取って通知だけして終わると、実行数の画面では「完了」になり、通知メールも届きません。エラーは握りつぶさず、知らせたうえで失敗として残します。WebhookのURLは秘密の値なので、コードに直書きせず、「プロジェクトの設定」の「スクリプト プロパティ」に置いて PropertiesService で読む形にしておくと、コードを人に見せるときに漏れません。
着信Webhookは、Chatを使えるBusinessまたはEnterpriseのGoogle Workspaceで使え、管理者が許可していないと「Webhook を追加」が出ません。1つのスペースへの投稿は1秒に1件までです。

トリガーの持ち主が退職・権限変更したとき
インストール型のトリガーは、作ったアカウントの権限で動きます。ほかのアカウントからは、そのトリガーは見えません。この性質から、次のことが起きます。
| 起きたこと | GASに起きること | 先にやっておくこと |
|---|---|---|
| トリガーを作った人のアカウントを停止・削除した | 実際に運用していると、そのアカウントで動いていたトリガーが止まり、通知メールも誰にも届かない | 退職の手続きの前に、トリガーを別のアカウントで作り直す |
| 作った人を共有ドライブやスプレッドシートの共有から外した | 開けなくなったファイルへの処理が権限のエラーで止まる | 共有を外す前に、そのアカウントのトリガーを洗い出す |
| 作った人が別の部署(組織部門)に移った | 移った先の設定でGmailや外部共有が制限されていると、その処理が止まる | 異動の前にトリガーを持ち替える |
他人のトリガーは見えないので、「誰が何のトリガーを持っているか」は台帳で持つしかありません。サンプル料理教室では、事務の共有ドライブに「GAS台帳」のスプレッドシートを置き、プロジェクト名・関数・トリガーの種類・持ち主・何をしているか、を1行ずつ書いています。退職の手続きでは、この台帳でその人の行を探し、代表のアカウントでトリガーを作り直してから、元のトリガーを消してもらいます。自分のトリガーは、script.google.com の左の「マイトリガー」で全プロジェクト分を一覧できるので、退職する人にこの画面を開いてもらって台帳と照らします。
トリガーの種類と持ち主の話は「GASのトリガー:定期実行・フォーム送信時・編集時」で、退職者のアカウントの扱いは「セキュリティの初期設定:2段階認証・外部共有の制限・退職者のアカウント」で扱っています。

GASで無理なときに n8n へ移す目安
GASは、Google Workspaceの中で完結する処理なら十分に強い道具です。止まる原因の多くは、Googleの外とのやり取りが増えたときに出てきます。次に当てはまる数が増えてきたら、n8n へ移すことを考えます。
| 目安 | GASで続けるなら | n8nに移すと |
|---|---|---|
| 外のサービス(LINE・kintone・決済など)を3つ以上呼ぶ | エラー処理と再試行を全部自分で書く | ノードごとに失敗が見え、再実行もできる |
| 1回の処理が6分を超えるのが当たり前 | 分割と再開を自分で組む | 実行時間の上限の考え方が違う。サーバーの性能次第 |
| 1分おきのような高頻度の監視 | トリガーの合計時間の上限に近づく | スケジュールのトリガーで細かく回せる |
| 外からのWebhookを受けて処理する | ウェブアプリとして公開し、権限と応答を自分で作る | Webhookのノードで受けられる |
| 処理の流れをGASを書かない人も見たい | コードを読める人しか追えない | 画面で流れが見える |
| 何十件をまとめて外のAPIに流す | 呼び出しの間隔を自分で調整する | Loop Over Itemsのノードで件数を区切って流せる |
サンプル料理教室では、予約一覧・カレンダー・リマインド・請求書のように、Google Workspaceの中で閉じているものはGASのままにし、問い合わせをLINEとChatに同時に流す、LINEで受けた予約をスプレッドシートに入れる、といった外とつなぐ部分を n8n に置いています。全部を一度に移す必要はありません。止まりやすいところから移します。n8n は自分でサーバーを持つか、提供元のクラウドを契約して動かすので、その運用の手間も比べたうえで決めます。具体的なつなぎ方は「Google Workspaceとn8nをつなぐ:フォーム・Gmail・スプレッドシートを外へ流す」で書いています。
できないことと、その代わり
| できないこと | 代わりの手 |
|---|---|
| 1回の実行を6分より長くする | 処理を分けて続きから再開する。外の仕組み(n8nなど)に移す |
| 割り当てを申請して増やす | 試用期間を終えて有料にする。それでも足りなければ処理を減らすか分ける |
| 別のアカウントのトリガーを自分の画面で見る | 「GAS台帳」で持つ。本人に「マイトリガー」を開いてもらう |
| 通知メールを持ち主以外に届ける | try・catch でChatやグループのアドレスに自分で送る |
| 実行数の記録を長く残す | 大事な実行はシートに日時と件数を書く。標準のCloudプロジェクトにつないでCloud Loggingで見る |
| 時間主導型のトリガーを分単位の時刻ぴったりに動かす | 時刻の幅を前提に組む。ぴったりが要るなら外のスケジューラを使う |
よく詰まるところ
| 症状 | 原因 | 直し方 |
|---|---|---|
| 止まっているのに通知メールが来ない | トリガーの持ち主が別の人、またはエラーを catch して握りつぶしている | 「マイトリガー」で持ち主を確かめる。catch の最後で throw する |
| 手で実行すると動くが、トリガーだと止まる | トリガーの持ち主が承認していない、または持ち主にファイルの権限が無い | 持ち主のアカウントでエディタから1回実行して承認する。ファイルの共有を確かめる |
| トリガーで動いた分のログが見つからない | エディタ上部の「実行ログ」を見ている | 左の「実行数」でその時刻の行を開く |
| 「実行数」に何も出ていない | トリガーがそもそも動いていない。編集時トリガーはGASやフォームの書き込みでは動かない | 「トリガー」の画面で有効かを見る。フォームの分は「フォーム送信時」にする |
| 毎朝9時のはずが9時40分に動く | 時間主導型のトリガーは1時間の枠の中で動く | 仕様として受け入れる。急ぐ処理は前の枠にずらす |
| 同じ処理が2回走って二重になる | トリガーが重複している、前の実行が終わる前に次が来た | 重複したトリガーを消す。LockService.getScriptLock() を入れる |
| 夕方になると毎日止まる | トリガーの合計実行時間の1日の上限に達した | 1分おきのトリガーを15分おきにする。処理をまとめて速くする |
| 新しいトリガーが作れない | 1ユーザー・1スクリプトのトリガーが20に達した | 続き用のトリガーの消し忘れを消す。作る前に同じ関数のトリガーを消す |
| 退職した講師が作ったフォームの自動返信が止まった | 講師のアカウントを停止したため、トリガーも止まった | 代表のアカウントでトリガーを作り直す。以後はGAS台帳で持ち主を管理する |
| デバッグ中に「現在の行のソースコードはありません」 | 動的に作られたコードにステップインした | ステップアウトするか、再開を押して進める |
| 昨日まで動いていたのに急に「サーバー エラー」 | Google側の一時的な障害のことがある | ステータスダッシュボードを見て、時間を置いて再実行する |
止まった原因を直したあとは、止まっていた間の分を必ず確かめます。リマインドなら、止まっていた日の分は手で送ったか。カレンダーなら、その間に入った予約が反映されているか。直して終わりにすると、止まっていた間の取りこぼしが後から問い合わせで見つかります。
次にやること
- Google Apps Script(GAS)とは:できること・始め方
- GASのトリガー:定期実行・フォーム送信時・編集時
- Google Workspaceとn8nをつなぐ:フォーム・Gmail・スプレッドシートを外へ流す
- セキュリティの初期設定:2段階認証・外部共有の制限・退職者のアカウント