- 2026.07.29 Apps Script 프로젝트가 늘어나면 반드시 망가지는 것들
- 2026.07.16 매일 돌던 Apps Script 트리거가 갑자기 멈추는 이유
- 2026.07.15 브라우저 저장소에 고객정보 넣으면 생기는 문제
- 2026.07.07 브라우저가 내 데이터를 버릴 때: 초록 체크가 거짓말을 한다
- 2026.07.07 설치됨 ≠ 작동함: 백업 트리거가 한 번도 안 돌았다 지금 보는 글
- 2026.07.07 GAS엔 점진 배포가 없다: '맞는' 검사 하나가 전 사용자를 잠근다
- 2026.07.07 시트가 텍스트를 날짜로 바꾼다: '2026-06'이 완료를 영영 막을 때
- 2026.06.18 여러 사람이 한 시트에 쓰는 점검 앱: 동시성·중복·내보내기 설계
- 2026.06.12 셀을 한 칸씩 읽지 마라: 서비스 호출이 진짜 비용이다
- 2026.06.11 시트 셀에 JSON·이미지를 통째로 넣으면 터진다
- 2026.06.10 웹앱 배포 설정 하나가 보안 모델을 정한다: Execute as / 접근 권한
- 2026.06.09 GAS 6분 실행 한도: 멈추기 전에 끊어서 재개하라
- 2026.06.08 PropertiesService는 데이터베이스가 아니다: 9KB의 벽
- 2026.06.07 모바일에서 화면을 이미지로 저장할 때 멈추는 진짜 이유
- 2026.06.06 동시에 같은 시트를 쓰면 깨진다: LockService와 30 동시 실행 천장
- 2026.06.05 재배포할 때마다 주소가 바뀌면 안 된다: /exec URL 고정
- 2026.06.04 외부 호출은 가끔 실패한다: 지수 백오프와 재시도 예산
- 2026.06.03 HtmlService 앱은 껍데기 먼저: 비동기 로드와 모바일 뒤로가기
- 2026.06.02 GAS를 졸업해야 할 때: 한계 신호와 외부 DB 이전
- 2026.06.01 GAS에 지능과 엔진룸을 더하기: Vertex AI와 Cloud Run
설치됨 ≠ 작동함: 백업 트리거가 한 번도 안 돌았다
GAS에서 스코프·트리거·권한은 에러 없이 조용히 실패한다. 매니페스트 스코프 하나가 빠지면 트리거가 한 번도 안 돈다. 왜 조용히 실패하는지, 멱등 설치로 20개 한도를 피하는 법, 하트비트와 '설치 직후 증명'까지.
GAS에선 “설치됨”이 “작동함”이 아니다. 스코프·트리거·권한은 에러를 안 내고 조용히 안 돈다. 백업 코드가 있었고 트리거도 “설치”했는데, 성공 기록이 단 한 번도 없었다. appsscript.json에 script.scriptapp 스코프 하나가 빠져서 트리거가 조용히 안 떴던 거다.

왜 중요한가
안 도는 걸 아무도 모른다. 사고가 터져 복구하려는 순간에야 “백업이 없다”는 걸 발견한다. 실효 복구 지점(RPO)이 무한이라는 뜻이다. 눈에 보이는 기능은 사용자가 리포트라도 하지만, 백업·동기·정리 같은 백그라운드 자동화는 조용히 죽으면 조용히 묻힌다.
왜 조용히 실패하나
세 가지가 대표적이고, 셋 다 에러를 안 낸다.
- 매니페스트 스코프 누락 — 트리거 생성엔
script.scriptapp이 필요하다. - 트리거 20개 한도 — 재설치를 반복하면 중복이 쌓여 조용히 실패한다.
- 재동의 안 됨 — 스코프를 바꾸면 사용자가 다시 승인해야 활성화된다.
스코프부터 확인하라. 없으면 트리거가 안 뜬다.
// appsscript.json — 트리거를 쓰려면 script.scriptapp이 반드시 있어야 한다
{
"timeZone": "Asia/Seoul",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/script.scriptapp"
]
}
정답: 멱등 설치 + 하트비트 + 헬스체크
설치는 멱등으로(같은 handler의 옛 트리거를 지우고 다시) 해서 20개 한도를 피한다. 그리고 실행마다 “마지막 성공 시각”을 남긴다.
const ADMIN = "[email protected]";
// 멱등 설치: 같은 handler의 기존 트리거를 지우고 새로 만든다 (중복·한도 방지)
function installBackupTrigger_() {
ScriptApp.getProjectTriggers()
.filter((t) => t.getHandlerFunction() === "runBackup")
.forEach((t) => ScriptApp.deleteTrigger(t));
ScriptApp.newTrigger("runBackup").timeBased().everyHours(6).create();
}
// 백업이 돌 때마다 '마지막 성공 시각'을 남긴다 = 하트비트
function runBackup() {
doBackup_();
PropertiesService.getScriptProperties()
.setProperty("backup.lastOk", new Date().toISOString());
}
// 헬스체크(별도 트리거): 하트비트가 오래됐으면 '설치됨'이 아니라 '죽음'
function assertBackupAlive_() {
const last = PropertiesService.getScriptProperties().getProperty("backup.lastOk");
const ageMs = last ? Date.now() - new Date(last).getTime() : Infinity;
if (ageMs > 26 * 60 * 60 * 1000) { // 26시간 넘게 성공 없음
MailApp.sendEmail(ADMIN, "백업 하트비트 끊김", "last=" + last);
}
}
설치 직후에 증명하라
“설치했다”로 끝내지 마라. 설치 직후 한 번 실제로 돌려서 하트비트가 찍히는지 확인한다. 이게 스코프 누락·재동의 실패를 그 자리에서 잡는다.
// 설치가 아니라 '한 번이라도 성공'을 증거로 삼는다
function installAndProve_() {
installBackupTrigger_();
runBackup(); // 즉시 1회 실행
const ok = PropertiesService.getScriptProperties().getProperty("backup.lastOk");
if (!ok) throw new Error("BACKUP_NEVER_RAN"); // 하트비트 없으면 설치 실패로 간주
}
놓치기 쉬운 것
- 매니페스트 스코프. 트리거 생성은
appsscript.json의oauthScopes에script.scriptapp이 있어야 한다. - 성공을 기록하라, 설치가 아니라. “트리거 만들었음” 로그는 증거가 아니다. “최근 성공 시각”만이 살아있음의 증거다.
- 트리거 20개 한도. 재설치를 반복하면 중복이 쌓여 조용히 실패한다. 만들기 전에 같은 handler의 옛 트리거를 지워라(멱등 설치).
- 스코프 바꾸면 재동의. oauthScopes 변경 후 한 번은 사람이 실행해 권한을 다시 승인하라. cron만 돌면 조용히 실패한다.
깊이: 눈에 안 보이는 자동화일수록 하트비트를
백업·동기·정리처럼 사용자가 안 보는 자동화는 전부 마지막 성공 시각을 남기고, 그 값이 끊기면 알람하라. “설치했다”는 과거형이고, “26시간 내 성공”은 현재형이다. 핵심 한 줄: 자동화는 “설치됨”이 아니라 “마지막 성공 시각”으로만 살아있다고 판정하라.
자주 묻는 질문
- Apps Script 트리거를 설치했는데 안 도는 이유는?
- appsscript.json에 script.scriptapp 스코프가 빠졌거나, 트리거 20개 한도에 걸렸거나, 스코프 추가 후 권한 재동의가 안 된 경우입니다. 공통점은 에러 없이 조용히 안 돈다는 것이라, 하트비트 없이는 발견이 늦습니다.
- 백업이나 자동화가 실제로 도는지 어떻게 확인하나요?
- 매 실행마다 '마지막 성공 시각'을 스크립트 속성에 기록하고, 그 값이 오래되면 알람하는 헬스체크를 별도 트리거로 둡니다. '설치했다'가 아니라 '최근에 성공했다'로 살아있음을 판정하세요.
- GAS에서 트리거 생성에 필요한 스코프는 뭔가요?
- 시간 기반 트리거 등 ScriptApp의 트리거 API를 쓰려면 appsscript.json의 oauthScopes에 script.scriptapp이 있어야 합니다. 빠지면 설치 자체가 실패하거나 트리거가 뜨지 않습니다.
- 스코프를 추가했는데도 왜 안 되나요?
- oauthScopes를 바꾸면 사용자가 권한을 다시 동의해야 새 스코프가 활성화됩니다. 무인(cron)으로 돌리면 재동의가 안 돼 조용히 실패할 수 있으니, 스코프 변경 후 한 번은 사람이 실행해 재승인하세요.
- 2026.07.29 Apps Script 프로젝트가 늘어나면 반드시 망가지는 것들
- 2026.07.16 매일 돌던 Apps Script 트리거가 갑자기 멈추는 이유
- 2026.07.15 브라우저 저장소에 고객정보 넣으면 생기는 문제
- 2026.07.07 브라우저가 내 데이터를 버릴 때: 초록 체크가 거짓말을 한다
- 2026.07.07 설치됨 ≠ 작동함: 백업 트리거가 한 번도 안 돌았다 지금 보는 글
- 2026.07.07 GAS엔 점진 배포가 없다: '맞는' 검사 하나가 전 사용자를 잠근다
- 2026.07.07 시트가 텍스트를 날짜로 바꾼다: '2026-06'이 완료를 영영 막을 때
- 2026.06.18 여러 사람이 한 시트에 쓰는 점검 앱: 동시성·중복·내보내기 설계
- 2026.06.12 셀을 한 칸씩 읽지 마라: 서비스 호출이 진짜 비용이다
- 2026.06.11 시트 셀에 JSON·이미지를 통째로 넣으면 터진다
- 2026.06.10 웹앱 배포 설정 하나가 보안 모델을 정한다: Execute as / 접근 권한
- 2026.06.09 GAS 6분 실행 한도: 멈추기 전에 끊어서 재개하라
- 2026.06.08 PropertiesService는 데이터베이스가 아니다: 9KB의 벽
- 2026.06.07 모바일에서 화면을 이미지로 저장할 때 멈추는 진짜 이유
- 2026.06.06 동시에 같은 시트를 쓰면 깨진다: LockService와 30 동시 실행 천장
- 2026.06.05 재배포할 때마다 주소가 바뀌면 안 된다: /exec URL 고정
- 2026.06.04 외부 호출은 가끔 실패한다: 지수 백오프와 재시도 예산
- 2026.06.03 HtmlService 앱은 껍데기 먼저: 비동기 로드와 모바일 뒤로가기
- 2026.06.02 GAS를 졸업해야 할 때: 한계 신호와 외부 DB 이전
- 2026.06.01 GAS에 지능과 엔진룸을 더하기: Vertex AI와 Cloud Run