GEMINI LABJP
PRO35 — July 17, the date reports had pointed to, has passed without an official Gemini 3.5 Pro announcement or model card. July 24 is being cited as the fallbackNB2LITE — Nano Banana 2 Lite, otherwise known as Gemini 3.1 Flash-Lite Image, arrives as the fastest of the family: roughly four seconds per image at $0.034 per thousandOMNI — Gemini Omni Flash enters public preview, generating video up to ten seconds long at $0.10 per second of outputEDIT — Omni Flash is built around conversational editing. Swap a character, relight a scene, or change the angle in plain language, and the original audio and video tracks stay intactSYNTHID — Both new models carry SynthID watermarking, so anything they produce can be checked for provenance from inside the Gemini appSHUTDOWN — The older image generation models are deprecated and switch off on August 17. Worth checking your migration windowPRO35 — July 17, the date reports had pointed to, has passed without an official Gemini 3.5 Pro announcement or model card. July 24 is being cited as the fallbackNB2LITE — Nano Banana 2 Lite, otherwise known as Gemini 3.1 Flash-Lite Image, arrives as the fastest of the family: roughly four seconds per image at $0.034 per thousandOMNI — Gemini Omni Flash enters public preview, generating video up to ten seconds long at $0.10 per second of outputEDIT — Omni Flash is built around conversational editing. Swap a character, relight a scene, or change the angle in plain language, and the original audio and video tracks stay intactSYNTHID — Both new models carry SynthID watermarking, so anything they produce can be checked for provenance from inside the Gemini appSHUTDOWN — The older image generation models are deprecated and switch off on August 17. Worth checking your migration window
Articles/Workspace
Workspace/2026-06-16Advanced

Processing Thousands of Sheet Rows with Gemini from Apps Script — Beating the 6-Minute Limit with Chunking and Idempotency

How to stop Apps Script batch jobs from dying at the 6-minute limit: trigger-based continuation, a status-column idempotency design, and exponential backoff that carries thousands of rows to completion.


Premium Article

I once tried to classify and summarize 2,000 app reviews sitting in a spreadsheet with Gemini in one pass, and the run was cut off at six minutes with nothing to show for it. As an indie developer running several apps, I keep reaching for Google Sheets to draft review replies or tidy up multilingual store metadata, because it's the surface I already live in. But when you write the obvious Apps Script loop, it dies the moment the row count grows. Worse, you don't know where it stopped, so rerunning re-processes from the top and double-bills the API. This article walks through the design that removes both walls at once, following code I actually run.

The six-minute cutoff is the first wall

A single Apps Script execution has a hard time limit: about six minutes on a consumer Google account, and about 30 minutes on a Google Workspace account. If you call Gemini once per row inside a loop, each row takes one to three seconds, so a free account hits the ceiling somewhere around 150–300 rows.

The tempting fix is to strip out every Utilities.sleep() and just go faster. I tried that first, and I think it's the wrong instinct. Speed doesn't remove the ceiling — double the rows and you stall again. The real fix is to stop trying to finish everything in one execution. You stop yourself inside a time budget and hand the rest to the next execution. Apps Script gives you time-based triggers precisely for this.

So the plan looks like this:

  1. Give the sheet a column that records processing state, so the data itself remembers how far you got
  2. Have each execution stop on its own before the clock runs out
  3. Just before stopping, schedule a trigger that re-invokes the same function a few seconds later, passing the baton

When those three fit together, a job of any size flows to completion, split across however many executions it needs.

The starting point is making every row idempotent

The real danger in split execution is double-processing on resume. If the dying run and the next run disagree about "how far did we get," you call Gemini twice on the same row. The most reliable guard is to write progress not into a script variable, but into the cell itself.

Concretely, add one status column next to your output column. It holds exactly three states: done, error, or empty. When you pick work, you only take rows where status is empty. Now, no matter when an execution dies, the next one naturally resumes from rows nobody has touched. Because nothing relies on script memory, even if a trigger fires twice, the row one run marked done simply drops out of the other run's candidates.

// Config: adjust to your own sheet
const CFG = {
  SHEET: 'reviews',     // target sheet name
  COL_INPUT: 1,         // input text column (A=1)
  COL_OUTPUT: 2,        // column to write Gemini's result (B=2)
  COL_STATUS: 3,        // processing-state column (C=3)
  HEADER_ROWS: 1,       // number of header rows
  TIME_BUDGET_MS: 4.5 * 60 * 1000, // bail at 4.5 min, short of the 6-min cap
  TRIGGER_DELAY_MS: 30 * 1000,     // resume 30 seconds later
};
 
// Take only "pending" rows where status is empty, top to bottom
function nextPendingRows_(sheet, limit) {
  const lastRow = sheet.getLastRow();
  if (lastRow <= CFG.HEADER_ROWS) return [];
  const n = lastRow - CFG.HEADER_ROWS;
  const status = sheet.getRange(CFG.HEADER_ROWS + 1, CFG.COL_STATUS, n, 1).getValues();
  const input = sheet.getRange(CFG.HEADER_ROWS + 1, CFG.COL_INPUT, n, 1).getValues();
  const rows = [];
  for (let i = 0; i < n && rows.length < limit; i++) {
    if (!String(status[i][0]).trim() && String(input[i][0]).trim()) {
      rows.push({ row: CFG.HEADER_ROWS + 1 + i, text: String(input[i][0]) });
    }
  }
  return rows;
}

The key is that selection keys off status, not the output column. If you decide "done" by whether the output cell is filled, a row where Gemini returned an empty string gets mistaken for pending. Holding state in a dedicated column also makes eyeballing the sheet afterward much easier.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
If your batch kept dying at the Apps Script 6-minute limit, you can now carry thousands of rows to completion via trigger continuation
You can swap a setup that re-processed the same rows on every rerun for a status-column idempotency design that never double-bills the API
You'll get copy-ready exponential backoff and per-row error isolation for row-level Gemini calls that used to stall on 429s
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Workspace2026-07-16
I Put =GEMINI() in 420 Cells. The Calls Kept Coming on Days I Never Opened the Sheet
A custom function calling Gemini from Sheets looks best the moment it starts working, and it does not grow from there. Here is what the 30-second ceiling, uncontrolled recalculation, and read-only cells actually cost, measured, plus the code to move to a write-back design.
Workspace2026-07-15
The Table Was There, but the Rows and Columns Weren't — Preserving Docs Structure Before It Reaches Gemini
getBody().getText() flattens Google Docs tables into a column of loose values. Here is what that cost me on a 42-row ledger, the Apps Script extraction layer that keeps the structure, and the acceptance test that keeps it honest.
Workspace2026-07-13
Retiring the Poll That Waits on an Overnight Batch — An Apps Script doPost Sink for Gemini Signals
Polling a Gemini batch or long-running operation every five minutes from an Apps Script time trigger quietly stacks up UrlFetch calls and latency. Receive the webhook in doPost, treat it as an unverified signal, then confirm authoritatively and apply idempotently.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →