The 6-Minute GAS Limit: Chunk and Resume Before It Kills You
Apps Script kills any single run at 6 minutes. Design bulk jobs as chunk + cursor + time-trigger resume, or you get the half-processed-data accident.
A single execution is force-killed at 6 minutes. Try to process 10,000 rows in one loop, hit the limit, and the script dies mid-run: leaving you half-processed. Run it again and it re-touches rows it already handled.
Why it matters
Half-applied data is worse than untouched data. You don’t know how far it got, so you need manual recovery: and that time eats the time automation was supposed to save. Trigger runtime also has its own daily cap (roughly 90 min/day on a consumer account).
The fix: chunk + cursor + trigger resume
Don’t try to finish in one shot. Save your position (a cursor) and use a time trigger to resume the next chunk.
const JOB_KEY = "sync.cursor";
function runSyncJob() {
const lock = LockService.getScriptLock();
if (!lock.tryLock(10000)) return; // prevent overlap
try {
const props = PropertiesService.getScriptProperties();
const cursor = Number(props.getProperty(JOB_KEY) || 0);
const result = processChunk(cursor, 300); // 300 rows at a time
if (result.done) {
props.deleteProperty(JOB_KEY);
deleteTriggers_("runSyncJob"); // clean up when finished
return;
}
props.setProperty(JOB_KEY, String(result.nextCursor));
scheduleOnce_("runSyncJob", 60 * 1000); // resume in 1 min
} finally {
lock.releaseLock();
}
}
function scheduleOnce_(handler, delayMs) {
deleteTriggers_(handler);
ScriptApp.newTrigger(handler).timeBased().after(delayMs).create();
}
function deleteTriggers_(handler) {
ScriptApp.getProjectTriggers()
.filter((tr) => tr.getHandlerFunction() === handler)
.forEach((tr) => ScriptApp.deleteTrigger(tr));
}
Easy to miss
- Always delete triggers. You get 20 triggers per script. If you don’t delete the existing same-handler trigger before creating a one-shot, you hit the cap fast.
- Prevent overlap. Use
LockServiceso a trigger that overlaps the previous run doesn’t process the same cursor twice. - Clean up on finish. Delete the cursor and trigger when done, or an empty job keeps firing every minute.
Deeper: size chunks by measuring
Start chunk size at 100–500 rows and tune against real run time. If one chunk passes half of the 6 minutes, shrink it. One line to keep: split into units that finish under 6 minutes, save your place, and resume.
Frequently asked questions
- Why does my Google Apps Script stop at 6 minutes?
- Apps Script force-kills any single execution at about 6 minutes (consumer accounts). Process a bulk job in one loop and you hit the limit mid-run, leaving the data half-applied.
- How do I get around the 6-minute execution limit?
- Don't finish in one run. Save your position as a cursor, then resume the next chunk with a time-based trigger (chunk + cursor + trigger resume). Use LockService to prevent overlapping runs.
- Do triggers remove the time limit?
- No. Total trigger runtime is also capped (about 90 minutes/day on consumer accounts). Size your chunks and run frequency to stay within that budget.