朝までにスプレッドシートを更新するはずのスクリプトが、今日は動いていない。
かと思えば別の日は、同じ処理が2回走ってメールが2通届いたり、
行が重複して追加されたりする。エディタで実行すると正常に終わる。
それなのに、トリガー経由だと結果が安定しません。
トリガーは「動くか動かないか」の二択ではなく、
設定の種類・承認状態・重複登録の3つが絡み合って不安定になることが多いです。
順番に確認していきます。
■ 時間主導型と編集時トリガーは、そもそも仕組みが違う
トリガーの一覧画面(左メニューの時計アイコン)を開くと、
「時間主導型」と「スプレッドシートから - 変更時/編集時」が並んで見えますが、
これらは動く仕組みが別物です。
編集時トリガー(onEdit)は、ユーザーがセルを実際に編集した瞬間に発火します。
そのため、数式の再計算による値の変化や、他のスクリプトによる書き換えでは
発火しません。 「セルの値が変わったのにトリガーが動かない」という相談の多くは、
値を変えたのが人間の手入力ではなく、別の数式やAPI経由の書き込みだったという
ケースです。値の変化そのものではなく、「編集という操作」に反応する仕組みだと
理解しておくと、切り分けが早くなります。
時間主導型トリガーは、指定した時刻・間隔でGoogleのサーバー側が実行するため、
編集の有無とは無関係に動きます。ただし、実行時刻は指定した時刻ぴったりではなく、
前後十数分の幅を持って実行される仕様があります。「毎日9時に動くはずが
9時15分に動いた」という現象自体は異常ではありません。
■ 承認が切れて止まっているケース
トリガーは一度設定すれば永続的に動き続けるように見えますが、
スクリプトが使う権限(外部サービスへのアクセス、Gmail送信など)が
変わった場合や、Googleアカウント側でのセキュリティ確認が入った場合、
承認が無効になり、トリガーがサイレントに止まることがあります。
止まっていても一覧画面からは分かりにくいため、トリガーを一度削除して
作り直し、手動で1回実行してオーナー権限の再承認ダイアログが出るかを
確認してください。ダイアログが出た場合、それまでは承認切れの状態で
静かに失敗していたことになります。
■ 同じトリガーが複数登録されていて、二重に動く
「動かない」より厄介なのが、気づかないうちに同じ関数に対するトリガーが
複数登録されていて、1回のイベントで2回・3回と実行されるケースです。
原因はだいたい次のどちらかです。
・コードを書き直すたびに `ScriptApp.newTrigger(...)` を含む
「トリガーを作成する関数」を毎回実行してしまい、削除しないまま
新しいトリガーを積み増している
・複数人でスクリプトを編集していて、それぞれが別のタイミングで
同じトリガー設定用の関数を実行している
トリガーの一覧画面には、同じ関数名・同じ種類のトリガーが
複数行並んでいても、見た目にはほとんど区別がつきません。
行数を数えて明らかに多い場合は、重複を疑ってください。
■ 棚卸しは `ScriptApp.getProjectTriggers()` が確実
トリガー一覧画面は見た目の確認には便利ですが、
プロパティ(作成日時やIDなど)までは見えません。
確実に棚卸しするには、スクリプトエディタで次のようなコードを
一度実行し、ログに出力します。
```
function listTriggers() {
const triggers = ScriptApp.getProjectTriggers();
triggers.forEach(t => {
Logger.log(t.getHandlerFunction() + " / " + t.getEventType() + " / " + t.getUniqueId());
});
Logger.log("トリガー数: " + triggers.length);
}
```
同じ `getHandlerFunction()`(実行される関数名)が複数行出てくれば、
それが二重実行の原因です。削除するときは、一覧画面から該当行を
選んで削除するか、`ScriptApp.deleteTrigger(t)` を該当のトリガーオブジェクトに
対して呼び出します。削除は1つずつ確認しながら行い、
一括削除で必要なトリガーまで消さないよう注意してください。
■ 単純トリガー(onEdit)とインストール型トリガーは、できることが違う
スクリプトファイルの中に `function onEdit(e) { ... }` とだけ書いておくと、
何もトリガーを設定しなくても編集のたびに自動で動きます。これは
「単純トリガー」と呼ばれる仕組みで、手軽な反面、セキュリティ上の制約から
できることが限られています。 Gmailの送信、外部サービスへのHTTPリクエスト
(`UrlFetchApp`)、他人が所有するファイルへのアクセスなど、
承認が必要な操作を単純トリガーの中で呼び出すと、エラーメッセージも
出ないまま、その部分だけ黙って実行されずに終わります。
これに対して、トリガー一覧画面から `onEdit` を明示的に「インストール型」の
編集時トリガーとして登録すると、承認済みの権限で動くため、
メール送信などの操作も問題なく実行できます。「onEditという名前の関数を
書いたのに、メールだけ送られない」という相談は、単純トリガーのまま
使っていて権限が足りないケースがほとんどです。関数名が同じ `onEdit` でも
中身の挙動が違う点に注意してください。
■ 実行数の上限に達していないか
Googleアカウントの種類(個人アカウントか組織のアカウントか)によって、
1日に実行できるトリガーベースの処理には上限があります。
上限に達すると、その日以降のトリガーはエラーも通知もないまま
実行がスキップされます。 「昨日までは動いていたのに今日だけ動かない」
という場合、他のスクリプトも含めて同じアカウント内で
実行数を消費しすぎていないかを確認する価値があります。
■ エラーが起きても気づけない仕組みになっている
トリガー経由の実行でスクリプトがエラー終了した場合、
既定ではスクリプトの所有者宛てにGoogleから通知メールが届きますが、
これは頻度によってまとめて送られたり、他の通知に埋もれたりしがちです。
確実に気づきたい場合は、スクリプトエディタの「トリガー」設定で
失敗時の通知頻度を「今すぐ通知」に変更するか、
`try...catch` で処理を囲み、失敗時に自分宛てのメールやチャット通知を
明示的に送る処理を入れておくと、通知メールに気づかず放置する事故を
減らせます。
■ やってはいけないこと
「動かないから」といって、確認せずにもう1つ同じトリガーを追加すること。
承認切れや上限が原因の場合、トリガーを増やしても状況は変わらず、
むしろ後で二重実行の原因を1つ増やすことになります。
まず `ScriptApp.getProjectTriggers()` で現状を確認してから対処してください。
■ それでも解決しないとき
・棚卸しをしても重複が見当たらないのに、二重実行が起きる
・承認をやり直しても、特定の時間帯だけ止まる
・複数のスクリプトファイルにまたがっていて、どのトリガーがどの処理を
呼んでいるか自分では追えない
このあたりまで来たら、コードとトリガー設定を突き合わせて
1つずつ検証した方が早いです。
私はGoogle Apps Scriptまわりの調査と修正を承っています。
どのトリガーが何を引き起こしていたかを突き合わせてお返ししています。
――
この記事の執筆には生成AIを活用しています。内容は実際の挙動を確認して書いています。