GASで作ったAirtable自動化が止まる7つの原因と、止めないための設計
GASとAirtable APIを繋ぐ自動化は、作るだけなら半日でできます。問題はその後で、数週間後にエラーを出さないまま静かに止まります。この記事では本番運用で実際に遭遇した7つの止まり方(6分の壁/トリガーが作成者アカウントで動く/人が触れる場所のフラグ/列名変更/二重実行/レート制限/エラーが誰にも見えない)を挙げ、それぞれを設計でどう防ぐかを、公式の制限値と実コードつきで解説します。
Google Apps Script(GAS)とAirtable APIを繋ぐと、月額ツールを増やさずに業務を自動化できます。作るだけなら、いまはAIに聞けば半日で動くものができます。 問題はその後です。数週間後、誰も気づかないうちに止まります。 しかもエラーは出ません。 この記事は作り方の解説ではありません。実際に本番で動かしてきた中で遭遇した「止まり方」を7つ挙げて、それぞれを設計でどう防ぐかを書きます。コード付きの実装手順はAirtableのAIエージェントの作り方(note)にまとめてあるので、そちらをご覧ください。 先に結論をお伝えします。GASの自動化で一番怖いのはエラーで落ちることではなく、エラーを出さずに静かに何もしなくなることです。
この記事でわかること
- GASの実行時間6分の壁に、どこでぶつかって、どう分割するか
- トリガーは作成者のアカウントで動き、他の人からは見えないという仕様がもたらす事故
- 人が触れる場所に「処理済み」のフラグを置くと、いつか必ず人が触る
- 列名を変えただけで全部止まる問題と、その構造的な防ぎ方
- Airtable APIのレート制限(公式値)とGASのクォータ(公式値)
- Automationsで済む範囲と、GASが必要になる境界
前提──公式の制限値を先に押さえる
設計の前に、動かしている土台の上限を把握しておきます。すべて公式ドキュメントの値です。
| 項目 | 値 | 出典 |
|---|---|---|
| GASの実行時間 | 1実行あたり6分(無料・Workspaceとも同じ) | Google Apps Script 公式クォータ |
| UrlFetchの呼び出し | 無料 20,000回/日/Workspace 100,000回/日 | 同上 |
| トリガーの合計実行時間 | 無料 90分/日/Workspace 6時間/日 | 同上 |
| Airtable APIのレート制限 | 5リクエスト/秒(1ベースあたり) | Airtable 公式 |
| 超過したとき | 429が返り、30秒待たないと成功しない | 同上 |
| PAT全体の上限 | 50リクエスト/秒(同一ユーザーの全トラフィック) | 同上 |
この表の中で、実務で最初にぶつかるのは「6分」です。 残りは当分効いてきません。
先にお伝えしたいこと
⚠️ 認証方式に注意してください。 検索で出てくる2021〜2022年頃の実装記事は、`key...` で始まるAPI Keyを使っています。この方式は廃止済みです。現在はPAT(Personal Access Token)が必須です。古い記事のコードをそのまま動かすと認証で失敗します。

原因①──6分の壁。1回で全部やろうとする
GASは1回の実行が6分で強制終了されます。これは無料アカウントでもWorkspaceでも同じです。
実案件で、この制限が業務要求に正面からぶつかったことがあります。クライアントから「ワンクリックでデータの補完までできるなら、取得件数を1,000件まで増やして、帰宅前にボタンを押せば翌朝には最新のリストができている状態にしたい」という要望が出ました。
やりたいことは自然です。しかし1,000件を1回の実行で処理するのは6分では終わりません。このときお伝えした説明がこれです。「GASは一回の実行で6分(サーバー側から6分を超えると強制的に停止されます)しか動かせないので、処理を小規模に分けて、都度実行する必要があります」。
対策は、1回の実行で処理する件数に上限をつけることです。 たとえばGmailから取り込む処理なら、こうします。
javascript
// 15分おきの時間主導型トリガーで実行する
function gmailToAirtable() {
var pending = GmailApp.getUserLabelByName('crm/未処理');
var done = GmailApp.getUserLabelByName('crm/登録済み');
if (!pending || !done) return;
var threads = pending.getThreads(0, 20); // ★1回の実行で最大20件に区切る
threads.forEach(function (thread) {
// ...Airtableへ登録...
// 登録に成功したときだけラベルを付け替える
if (res.getResponseCode() === 200) {
thread.removeLabel(pending);
thread.addLabel(done);
}
});
}ポイントは `getThreads(0, 20)` の20です。 残りは次の実行(15分後)に回ります。未処理のものがラベルで残り続けるので、取りこぼしません。
補足
6分がどうしても足りない業務は、GASを使う前提自体を見直してください。 実際に私たちも、ある案件で「毎回実行ボタンを押さないといけないのが現実的でない」という理由で、GASをやめてWebアプリに作り替えたことがあります。クラウドサーバーなら60分以上の連続処理ができます。6分で割り切れない処理は、GASの適用外と判断した方が早いです。
原因②──トリガーは「作った人」で動いている
ここが最も事故になりやすい箇所です。GASのインストーラブルトリガーには、公式仕様として2つの性質があります。
トリガーの2つの仕様
- 1作成した本人のアカウントで実行され続ける(他のメンバーがファイルを開いて発火させても、実行主体は作成者のまま)
- 2他のアカウントからは、そのトリガーの存在が見えない
この2つが組み合わさると、こうなります。退職者が仕込んだ自動実行は、残されたチームには存在自体が見えません。 棚卸しをしようにも、一覧を出せるのは本人だけ。そしてアカウントが停止された瞬間、見えない何かが静かに止まります。エラー通知の宛先すら本人なら、止まったことに誰も気づきません。
実案件でも、この仕様が運用の判断に直結しました。メール送信を自動化する場面で、送信元が「トリガーを設定した人」になってしまうためです。結果として、個人アカウントではなく共用アカウントにログインさせてもらい、そこでトリガーを設定する、という段取りを取りました。
トリガーと認可の持たせ方
- 1システム用の共用アカウントを1つ作り、そこでトリガーと認可を作る(これが唯一の予防策)
- 2既に個人アカウントで動いているものは、「引き継ぐ」のではなく「作り直す」。実行主体は移せません
- 3退職・異動が決まったら、本人にトリガーの一覧を書き出してもらう。他人には見えない以上、本人にしかできない作業です
- 4エラー通知の宛先も個人メールにしない
この論点の詳細はAIで作った業務ツールは半年後に誰が直すのかにまとめています。
★原因③──人が触れる場所にフラグを置くと、人が触る
実案件で起きた、最も厄介な止まり方です。
メールをトリガーに情報を取り込む仕組みで、特定の送信元からのメールだけが取り込まれないという事象が起きました。エラーは出ていません。他のメールは正常です。一見すると動いています。
原因の切り分けに数日かかりました。答えはこうでした。実装が「未読メールの中から最新のものを探し、処理したら既読にする」という設計だったため、人間が先にそのメールを開くと、永久に処理対象から外れる。
興味深いのは、この可能性にクライアント側が先に気づいたことです。「共有アドレスのメールだと、誰かが開封済みにしてしまう可能性があるのではないか」という指摘でした。
このときこちらは、一度「運用でカバーする」案を出しています。毎週の作業前にメールボックスを見て、対象のメールが未読であることを確認する。誰かが既読にしていたら未読に戻してから実行する、という手順です。 これは筋の悪い提案でした。 人間の注意力に依存する運用は、必ずどこかで破れます。最終的に設計を変えました。
先にお伝えしたいこと
外部サービスの「状態」を制御フラグに流用しない。 既読・未読のような人間が触る状態をトリガーに使わず、処理済みラベルやシステム専用のプロパティといった、業務の人が触らない場所に印を付けます。 > 人間が触れる場所にフラグを置くと、いつか必ず人間が触ります。
前掲のGmailの例では `crm/登録済み` という専用ラベルを使っています。議事録の取り込みでは Script Properties(スクリプト専用の保存領域)に処理済みIDを持たせています。どちらも業務の人が日常的に触らない場所です。
⚠️ ちなみに、自社の営業メール送信GASはスプレッドシートのH列に「送信済み」と書く方式になっています。これは人が触れる場所です。社内用で件数も少ないため許容していますが、同じ設計をクライアントの業務に入れるべきではないと考えています。
原因④──列名を変えただけで、全部止まる
Airtableは画面から簡単にフィールド名を変えられます。現場の誰かが「次アクション」を「Next Action」に変えた瞬間、そこを参照している処理が全部止まります。
実案件では、この警告をクライアントに事前にお伝えしていました。「列名の変更や、列の増減、テーブルの追加によって、Automationの変数部分やリレーション関係も変わります」。それでも起きます。環境を複製したときには、テーブル名・カラム名の参照を全箇所直す作業が発生しました。
対策は、フィールドIDで参照することです。 Airtable APIは `returnFieldsByFieldId=true` を付けると、フィールドを名前ではなくID(`fldXXXXXXXX`)で扱えます。
javascript
var F = {
name: 'fldxxxxxxxxxxxxxx', // 案件名
status: 'fldxxxxxxxxxxxxxx', // ステータス
next: 'fldxxxxxxxxxxxxxx' // 次アクション
};
function fetchCases() {
var url = 'https://api.airtable.com/v0/' + BASE + '/' + TBL +
'?pageSize=50&returnFieldsByFieldId=true'; // ★IDで返させる
var res = UrlFetchApp.fetch(url, {
headers: { Authorization: 'Bearer ' + AIRTABLE_PAT },
muteHttpExceptions: true
});
// ...
}IDは画面から変更できません。 名前を自由に変えてもらって構わない状態になります。コードの可読性は少し落ちますが、上のように定数へまとめておけば実用上の問題はありません。 運用に入れるものは必ずIDで書く。地味ですが、これを知らずに作ると数週間後に必ず踏みます。
原因⑤──同じ処理が二重に走る
外部からの呼び出しをトリガーにすると、同じイベントが複数回届くことがあります。
Slackが典型です。Slackは3秒以内に応答がないとイベントを再送します。GASからAI APIを呼ぶ処理は普通に3秒を超えるので、対策しないと必ず多重実行になります。実際、最初のテストでBotが同じ返信を2回、3回と連投しました。
対策1は、受信した時点で即座に「受け取った」と返すことです。
javascript
function doPost(e) {
var body = JSON.parse(e.postData.contents);
if (body.type === 'event_callback') {
var cache = CacheService.getScriptCache();
if (cache.get(body.event_id)) {
return ContentService.createTextOutput('ok'); // ★再送は無視
}
cache.put(body.event_id, '1', 600); // 10分間おぼえておく
// ...本処理...
}
return ContentService.createTextOutput('ok');
}対策2は、もう一度実行されても壊れない作りにすることです。 カレンダー登録のように「実行するたびに増えてしまう」処理では、登録済みフラグを立てて次から対象外にします。
javascript
// 「カレンダー未登録」のレコードだけ取得する
var url = 'https://api.airtable.com/v0/' + BASE + '/' + TABLE +
'?filterByFormula=' + encodeURIComponent('{カレンダー登録済み} = 0');
// ...イベント作成後...
// 登録済みフラグを立てる(二重作成の防止)
UrlFetchApp.fetch(url2, {
method: 'patch',
payload: JSON.stringify({ fields: { 'カレンダー登録済み': true } }),
// ...
});自動化は「もう一度実行されても壊れない」作りにしておくのが鉄則です。
原因⑥──レート制限に当たっているのに気づかない
Airtableは1ベースあたり5リクエスト/秒で、超えると429が返り30秒待たされます(公式値)。
ループの中で1件ずつAPIを叩く実装は、件数が増えた途端にここへ当たります。しかも `muteHttpExceptions: true` を付けていると例外にならないので、見た目は正常に動いて中身だけ失敗します。
自社の営業メール送信GASでは、レート制限対策として明示的に待機を入れています。考え方は同じです。
javascript
/** 送信間隔(ミリ秒)。レート制限対策。 */
const SEND_INTERVAL_MS = 2000;
try {
// ...処理...
if (i < data.length - 1) {
Utilities.sleep(SEND_INTERVAL_MS); // ★次の処理まで待つ
}
} catch (e) {
errorCount++;
sheet.getRange(i + 1, 8).setValue("エラー: " + e.message); // ★行単位でエラーを残す
}先にお伝えしたいこと
⚠️ `muteHttpExceptions: true` は、エラーを握り潰すオプションではありません。 これを付けるなら、必ず `getResponseCode()` を見て分岐してください。付けっぱなしでレスポンスを見ない実装が、「静かな故障」の最大の発生源です。
原因⑦──エラーが誰にも見えていない
最後は仕組みの問題です。エラーが出ていても、それが誰にも届かなければ、止まっていないのと区別がつきません。
実案件では、GASをライブラリとして配布した結果、実行履歴が直接見られなくなるという事象もありました。対策として、ライブラリが呼ばれたあとのログをSlackに飛ばす仕組みを別途作っています。
対策は、エラーを隠さず人の目に入る場所へ出すことです。 AI APIの応答が想定外だったときも、中身をそのまま出して原因が見えるようにしています。
javascript
var data = JSON.parse(raw);
if (!data.content) {
// ★エラー時は中身をSlackに出して、原因が見えるようにする
return { action: 'query', reply: '⚠️ エラー: ' + raw.slice(0, 400) };
}エラーは隠さずSlackに出す。 エラーが見えれば現場から「壊れてる」と連絡が来ます。沈黙するBotが一番信頼を失います。
補足
できれば「成功通知」も入れてください。 エラー通知だけだと、仕組みが丸ごと止まったとき(=エラーすら発生しないとき)に気づけません。「毎朝9時に実行完了」という通知が来なくなったことで異常に気づける、という設計です。
そもそもGASを使うべきか──Automationsとの切り分け
ここまで読むと分かるとおり、GASは運用の手間がかかります。Airtable Automations(ノーコードの自動化)で済むなら、その方が保守は楽です。
| やりたいこと | 使うもの |
|---|---|
| 見たいだけ(他のテーブルのデータを参照) | 公式のSync機能 |
| 単純な通知・更新(レコードが追加されたらSlackに通知 等) | Automations |
| それ以上(双方向・繰り返し・集約・条件分岐・外部API) | GAS |

実際に私たちも、GASで組んでいた「カレンダー→Airtable→Slack」の処理について、Automationsで置き換えられるのではないかと社内で検討したことがあります。結論として、単純な連携部分は置き換え可能でした。
一方で、Automationsでは届かない境界もはっきりしています。Airtable側のAutomationはトリガーの種類に制限があり、たとえば特定のチャットチャンネルへのメッセージ流入をトリガーにはできません。そうした外部イベントを拾う必要がある場合は、チューニング性の高いGASとAirtableを組み合わせるのが現実的な解になります。
先にお伝えしたいこと
⚠️ 両方を使うと、両方を保守することになります。 実案件で「Automationは直したけど、GASも直さないといけない」という状況が起きました。同じ業務を2つの仕組みが分担していると、修正のたびに両方を追う必要があります。どちらか一方に寄せられるなら寄せた方がいい、というのが実感です。
なお、Automationsには月間の実行回数上限があります。件数の多い定型処理は、定時実行を1本だけ置いてスクリプトアクションでまとめて処理すると消費を1回に抑えられます。詳細はAirtable Automationsの実行上限にまとめました。
認証情報をどこに置くか
トークンの扱い
- 1スクリプトプロパティに置く(`PropertiesService.getScriptProperties()`)。コードに直書きしない
- 2チャットに貼らない。どうしても渡すなら、渡した後に再発行する前提で扱う
- 3誰のアカウントで発行したキーかを記録する。個人アカウント発行のキーは、その人の退職で死にます
- 4PATはスコープを最小にし、対象ベースを限定する
正直に書くと、実案件のチャットログを見返すとアクセストークンがそのまま貼られている場面が複数ありました。開発中のスピードを優先した結果ですが、チャットの検索履歴には永久に残ります。
補足
環境を1つにすると保守が楽になります。 以前は自社環境でツールを作り、不具合の報告を受けるたびに自社側を直してクライアント側へ反映する、という流れでした。環境が2つあると反映漏れが起きるし、再現確認もできない。 クライアントの環境に直接置いて、そこで動作確認と保守をする形に変えたところ、運用工数が大きく下がりました。
よくある質問
「GASとAirtable、どちらのレート制限に先に当たりますか?」
実務ではGASの6分が先に来ます。 Airtableの5リクエスト/秒は、1件ずつ処理する実装なら1秒間に5件も処理できないので、意外と当たりません。当たるのは「ループ内で複数回APIを叩く」「大量データを一気に流す」設計のときです。逆にGASの6分は、数百件規模で普通に超えます。
「`muteHttpExceptions: true` は付けるべきですか?」
付けてよいですが、必ずレスポンスコードを見てください。 このオプションはHTTPエラーで例外を投げなくするものです。付けると処理が止まらなくなる一方、失敗に気づけなくなります。付けるなら `getResponseCode()` で分岐し、200以外は通知する実装とセットにしてください。
「既に個人アカウントで動いているトリガーは、どう移せばいいですか?」
移せません。作り直しになります。 トリガーの実行主体は作成者に固定されており、所有権の移転はできません。手順としては、①本人にトリガー一覧を書き出してもらう ②システム用アカウントで同じトリガーを作り直す ③認可をやり直す ④元のトリガーを削除、となります。本人がいるうちにやってください。
「Airtable Automationsだけで完結させることはできますか?」
業務によります。レコードの追加・更新をきっかけにした通知や更新なら、Automationsで足ります。 届かないのは、①Automationsにトリガーが用意されていない外部イベント ②複雑な条件分岐や集約 ③外部APIとの往復、です。この3つに該当しないなら、GASを使わない方が保守は楽になります。
まとめ
止めないための7つの設計
- 16分で割り切る。1回の処理件数に上限を付け、残りは次の実行へ回す
- 2トリガーはシステム用アカウントで作る。個人アカウントのトリガーは他人から見えず、退職で止まる
- 3★処理済みのフラグを、人が触れる場所に置かない。既読・未読のような人間が触る状態を流用しない
- 4フィールドはIDで参照する(`returnFieldsByFieldId=true`)。列名は現場が変える
- 5二重実行の前提で作る。イベントIDのキャッシュ、登録済みフラグで二重実行を防ぐ
- 6レート制限を見る(Airtable 5req/秒・429で30秒待ち)。`muteHttpExceptions` を付けるならレスポンスコードを必ず確認する
- 7エラーを人の目に入る場所へ出す。できれば成功通知も入れ、「来なくなったこと」で気づけるようにする
作るのは半日でできます。半年後も動いている状態にするのが、実際の仕事です。 実装の手順とコード全文はAirtableのAIエージェントの作り方(note)に、保守を誰が担うかという論点はAIで作った業務ツールは半年後に誰が直すのかにまとめています。
同じ取り組みを、貴社でも。
「いま社内で動いているGASが、誰のアカウントで、何を見て動いているか分からない」という状態でしたら、棚卸しのやり方からお手伝いできます。現状を30分ほどお聞かせいただければ、危ない箇所はその場でお答えできます。資料も準備も要りません。まずは無料の壁打ちから、お気軽にどうぞ!!
AI×CRM構築サービスを見る他の記事