바이브 코딩으로 진짜 GAS 앱을 만들었다 — 데모는 멀쩡했고, 현장에서 다 터졌다
AI로 현장 점검 앱을 GAS로 만들었다. 데모는 잘 돌았다. 그리고 배포 버튼 하나에 전 현장이 잠기고, 시트가 날짜를 바꿔치기하고, 초록 체크가 거짓말을 했다. 실제로 하루 종일 잡은 버그들과, GAS로 바이브 코딩하기 전에 미리 신경 쓸 것들.
결론 먼저: AI한테 시켜서 현장 점검 앱을 GAS로 만들었다. 데모는 멀쩡했다. 근데 발주처(개발 모르는 분)가 현장에서 쓰기 시작하니까, 큰 게 아니라 작은 것들이 하나씩 무너졌다. 하루 종일 잡은 그 자잘한 버그들이, 사실 GAS로 뭘 만들 때(특히 바이브 코딩할 때) 미리 알아야 하는 지도다.

AI는 “데모가 도는” 코드를 잘 짠다. 문제는 GAS의 happy path 바깥이다 — 배포 모델, 시트 타입, 브라우저 저장소, 실행 한도. 여기서 터지는 건 코드 버그가 아니라 런타임을 몰라서 나는 사고다. 이 글은 내가 오늘 실제로 밟은 지뢰 6개를 순서대로 보여준다.
앱이 뭐였냐면
현장 담당자가 폰으로 여러 현장을 돌면서 점검하는 모바일 웹앱이다. 스택은 순수 바이브 코딩 스택 — 화면은 HtmlService가 뿌리는 단일 index.html, 데이터는 구글 시트, 사진은 드라이브, 겉은 Cloudflare로 PWA 래핑. 발주처는 코드를 모른다. 그래서 버그 리포트가 “에러 코드 500”이 아니라 “사진이 깨져 보여요”, “완료가 안 눌려요”로 온다. 그 말들을 하나씩 코드로 번역하는 게 오늘 일이었다.
1. “올렸는데” 안 올라갔다 — 그리고 올라간 순간, 전 현장이 잠겼다
두 개가 겹쳐서 왔다.
먼저 clasp push가 “already up to date”라는데 사용자는 계속 옛 화면을 봤다. GAS에서 push는 소스만 올리고, deploy가 버전을 자른다. 둘은 별개 단계다.
clasp push # 소스만 올라감 — 사용자는 여전히 옛 화면
clasp deploy # 이때 새 버전이 사용자에게 나간다

push만 하고 “배포됐다”고 착각하는 건 바이브 코딩의 국룰 실수다. AI한테 “배포해줘”라고 하면 push만 하고 끝내는 경우가 잦다 — 실제로 라이브가 됐는지는 사람이 확인해야 한다.
그리고 진짜 사고. “보안 강화”라고 세션 검사(requireApprovedSession_)를 붙여서 배포했다. 로컬 테스트 다 통과. 근데 이미 현장 폰에 깔린 PWA는 그 세션 토큰을 안 보낸다. 배포 즉시 전 현장의 이력 조회가 승인된 앱 세션이 필요합니다로 막혔다. GAS 배포엔 카나리(일부에게만 먼저)가 없다 — promote 하면 그 순간 100%에게 나간다.
그날 밤 바로 이전 버전으로 롤백하고, 새 검사를 3단 모드로 바꿔서 옛 클라이언트가 안 깨지게 다시 냈다.
// 새로 강제하는 검사는 기본을 compat로 — 옛 클라이언트를 안 깨뜨린다
if (ok) return;
if (mode === "compat") return; // 통과 (옛 클라 보호)
throw new Error("APP_SESSION_REQUIRED"); // enforce일 때만 차단
2. 시트가 내 ‘2026-06’을 날짜로 바꿔놨다
발주처: “6월 30일 점검을 7월 1일에 입력하면 완료가 영영 안 눌려요.”
원인은 시트였다. 셀에 "2026-06"을 넣으면 구글 시트가 그걸 텍스트가 아니라 Date로 조용히 강제 변환한다.
// 저장·출력·완료가 같은 셀을 각자 다르게 읽는다
row[COL.month] // "2026-06"인 줄 알았지만 Date일 수 있음
new Date(row[COL.month]) // 어디선 Date로, 어디선 문자열로 → 세 값이 어긋남
저장·출력·완료 세 경로가 각자 “이게 몇 월이지”를 다르게 계산했고, 어제 날짜를 넣는 순간 세 계산이 어긋나서 완료가 영원히 거부됐다. AI가 짠 코드의 전형이다 — 함수 하나하나는 “합리적”인데, 시트가 타입을 바꾼다는 걸 아무도 몰라서 전체가 어긋난다.
3. 초록 체크가 거짓말을 하고 있었다
발주처: “사진이 깨져 보이는데 옆엔 초록색 완료 체크가 떠 있어요.”
이건 좀 소름이었다. 모바일 브라우저의 로컬 저장소(localStorage)는 용량이 작다. 압축한 사진 한 장이 그 한도를 넘으면, 브라우저가 사진 본문은 조용히 버리고 우리가 같이 저장한 filled: true 플래그만 남긴다. 그래서 앱은 “본문 없는데 채워짐” 상태가 됐고, 깨진 이미지 위에 초록 체크를 그려서 사용자한테 거짓말을 하고 있었다. 사진 본문은 IndexedDB로 옮기고, 본문이 없으면 초록 체크 대신 빨간 “다시 첨부 필요”를 띄우게 고쳤다.
4. 서버는 4분째 일하는데, 폰은 60초에 포기했다
GAS는 한 번 실행이 6분에서 강제 종료된다. 그래서 출력 생성에 4.5분 소프트 예산을 걸고, 넘으면 이미 저장한 건 건너뛰고 이어서 재개하게 짜뒀다. 근데 현장망이 느릴 때 클라이언트가 60초에 먼저 포기하고 “실패”를 띄운 뒤 통째로 재업로드를 시작했다. 서버는 멀쩡히 일하는 중인데.

서버 예산(270초)과 클라이언트 타임아웃(60초)이 안 맞으면, 느릴수록 더 느려지는 악순환이 생긴다. AI는 클라이언트 타임아웃을 그냥 둥근 숫자(60초)로 잡고, 서버가 실제로 몇 초까지 일하는지는 신경 안 쓴다.
5. 백업은 “설치”됐지만, 한 번도 안 돌았다
이게 제일 무섭다. 백업 코드가 있었다. 트리거 설치도 했다. 근데 성공 기록이 단 한 번도 없었다. appsscript.json에 script.scriptapp 스코프 하나가 빠져서 트리거가 조용히 안 떴던 거다. 에러도 안 났다 — 그냥 안 돌았다. “코드가 있다”와 “실제로 돈다”는 완전히 다른 얘기다. 그날부터 백업은 “설치했다”가 아니라 “마지막 성공 시각”으로만 믿기로 했다.
6. 그리고 목록이 통째로 사라졌다
며칠 전, 핵심 목록 시트의 데이터가 통째로 #REF!로 증발했다. 원인은 끝내 완전히 못 밝혔지만 방아쇠는 명확했다 — 커밋 안 한 워킹트리에서 프로덕션에 두 번 배포한 것. 복구 수단은 구글 시트 네이티브 버전 기록뿐이었고, 그건 아무것도 보장하지 않는다. 지금 이 프로젝트에 배포 전 preflight(더티 트리면 배포 거부)와 배포 증거 기록이 붙어 있는 건 전부 이 하루 때문이다.
위 6개 중 5개는 “코드가 틀려서”가 아니라 “GAS 런타임을 몰라서” 났다. 그리고 그게 정확히 AI가 못 채우는 부분이다.
그래서 GAS로 바이브 코딩하기 전에 신경 쓸 것
네 프로젝트에 그대로 붙여도 되는 체크리스트다.
- 배포 ≠ push.
clasp push는 소스만 올린다.deploy로 버전을 잘라야 사용자에게 나간다. AI한테 “배포”를 시켰으면 실제로 라이브가 됐는지 사람이 확인하라. 그리고 promote는 즉시 전역이니, 이전 안전 버전 번호를 항상 적어둬라. - 새 검사는 compat부터. 이미 배포된 클라이언트가 있으면, 새로 강제하는 게이트는 기본 compat → warn → enforce로. 한 번에 enforce로 내보내면 전 사용자를 잠근다. enforce 승격은 “이제 됐겠지”가 아니라 옛 클라이언트 히트가 0인지 데이터로 판단하라.
- 시트를 DB로 믿지 마라. 날짜·0으로 시작하는 숫자·긴 ID는 시트가 타입을 바꾼다. 쓸 땐 서식을 텍스트로 고정, 읽을 땐
[object Date]방어. 월 같은 값은 정규화 함수 하나로만 읽어라. - 저장 플래그와 데이터를 따로 믿지 마라. 브라우저 저장소는 쿼터가 차면 조용히 버린다. 큰 본문은 IndexedDB, 상태는 본문 존재로 판정. 앱 열 때 정합성 검사로 사라진 걸 알려라.
- 클라이언트 타임아웃 > 서버 예산. 6분 벽 안에서 쪼개 재개하되, 클라이언트가 서버보다 먼저 포기하지 않게 하라. 재시도는 “이미 된 건 건너뛰기”로.
- “설치됨”을 믿지 말고 하트비트를 요구하라. 스코프·트리거·백업은 조용히 실패한다. 설치 직후 한 번 실제로 돌려 성공을 확인하고, 마지막 성공 시각으로만 살아있다고 판정하라.
- 더티 트리로 프로드 배포 금지. 커밋하고, 이전 버전을 기록하고, 배포하라. 시트 데이터는 코드 롤백으로 못 되돌린다.
한 줄 요약
데모가 되는 것과 현장에서 버티는 것은 다르다. AI는 앞의 절반을 몇 시간에 해주지만, GAS의 배포 모델·시트 타입·브라우저 저장소·실행 한도는 네가 알아야 채워진다. 오늘 내가 하루 종일 밟은 지뢰가 정확히 그 목록이다.
더 궁금하면
네 프로젝트가 어디서 터질지 짚어주고 싶으면 편하게 물어보세요. 이 앱도 그렇게 하나씩 잡아온 거다. 전체 그림부터면 → 바이브 코딩 101 · 만든 걸 배포하는 법은 → localhost에서 어떻게 배포함?.
자주 묻는 질문
- GAS로 앱을 만들 때 가장 자주 터지는 문제는 뭔가요?
- 코드 자체보다 GAS 런타임의 특성에서 터집니다. 배포가 즉시 전 사용자에게 반영돼(카나리 없음) 새 검사 하나가 기존 클라이언트를 잠그고, 시트가 텍스트를 날짜로 강제 변환하고, 브라우저 저장소가 데이터를 조용히 버립니다. AI가 짠 코드는 이 함정들을 모릅니다.
- AI(바이브 코딩)로 만들면 이런 문제가 줄어드나요?
- 오히려 이 문제들은 AI가 특히 잘 놓칩니다. AI는 '데모가 도는' 코드를 빠르게 짜지만, 이미 배포된 클라이언트·6분 실행 한도·시트 타입 강제 변환 같은 운영 맥락을 모릅니다. 데모가 되는 것과 현장에서 버티는 것은 다릅니다.
- clasp push를 했는데 왜 반영이 안 되나요?
- push는 소스만 올리고, deploy가 실제 버전을 웹앱으로 자릅니다. push가 'already up to date'라고 해도 사용자는 여전히 옛 버전을 받습니다. 배포는 push와 deploy가 별개 단계입니다.
- 배포 전에 꼭 확인해야 할 것은?
- 커밋되지 않은 워킹트리에서 프로덕션에 배포하지 마세요. 배포 즉시 전 사용자에게 나가므로, 롤백 포인터(이전 안전 버전)를 기록하고, 새로 추가하는 검사는 기존 클라이언트를 잠그지 않도록 compat 모드로 먼저 내보내세요.