GEMINI LABJP
MODEL — Gemini 3.5 Flash reaches general availability and becomes gemini-flash-latestAPI — The Interactions API hits GA as the primary way to work with Gemini models and agentsAGENT — Managed Agents enter public preview, running stateful agents in isolated Linux sandboxesAPI — Background execution lands, letting you fire long-running jobs and collect results laterSEARCH — File Search now embeds and searches images natively via gemini-embedding-2NOTICE — Since June 19, requests from unrestricted API keys are blockedMODEL — Gemini 3.5 Flash reaches general availability and becomes gemini-flash-latestAPI — The Interactions API hits GA as the primary way to work with Gemini models and agentsAGENT — Managed Agents enter public preview, running stateful agents in isolated Linux sandboxesAPI — Background execution lands, letting you fire long-running jobs and collect results laterSEARCH — File Search now embeds and searches images natively via gemini-embedding-2NOTICE — Since June 19, requests from unrestricted API keys are blocked
Articles/Workspace
Workspace/2026-07-01Advanced

When Two Triggers Write at Once, Your Gemini Result Quietly Vanishes — A Durable Result Store for Apps Script

Storing Gemini results from several Apps Script triggers loses writes through read-modify-write races and PropertiesService size limits. Build a result store that survives, using LockService, a durable sink, and idempotency keys.

Apps Script4Gemini API157LockServicePropertiesServiceGoogle Workspace13

Premium Article

One morning I was reviewing an Apps Script job that wrote Gemini's overnight summaries back into a spreadsheet, and one of three jobs that had clearly run was simply missing from the state store. No error in the logs. The execution history showed two time-driven triggers firing within the same second. If you run several automations in parallel as an indie developer, this is the kind of trap you eventually step on.

The cause was neither Gemini nor an Apps Script bug. It was the ordinary "read, add, write back" update I had written myself. Here I'll reproduce why that update quietly breaks, then build a result store that survives concurrent triggers using LockService, a durable sink, and an idempotency key.

Why it disappears: the read-modify-write race

Start with the common pattern of keeping all results in one JSON blob.

function appendResultNaive(jobId, text) {
  const props = PropertiesService.getScriptProperties();
  const raw = props.getProperty('results') || '{}';
  const map = JSON.parse(raw);   // (A) read
  map[jobId] = text;             // (B) add
  props.setProperty('results', JSON.stringify(map)); // (C) write back
}

As long as only one trigger runs, this is fine. The trouble starts when two triggers reach (A) at nearly the same time. Both read the same map that does not yet contain jobId, each adds its own result, and each writes back at (C). Whichever execution reaches (C) last overwrites the result written first. That is a classic lost update, and the losing job vanishes without raising anything.

Apps Script is single-threaded within one execution, but triggers and concurrent runs execute as separate processes that can overlap. PropertiesService does nothing to protect the gap between your read and your write, and that gap is where the race lives.

Look the PropertiesService limits in the eye

Before fixing the race, settle what belongs in PropertiesService at all. The property store has firm ceilings, and crossing them makes setProperty throw.

StorePer-value limitTotal limitBest used for
PropertiesService~9 KB~500 KBSmall coordination state, flags, an index
CacheService~100 KB(volatile, up to 6 hours)A hot cache to speed up reads
Drive / SpreadsheetEffectively largeLargeA durable sink for the payload itself

A Gemini result is several KB even for a summary, and easily over 9 KB once it includes body text. So "accumulate the result body directly in PropertiesService" breaks on size before it ever races. If you want to measure a value before storing it:

function fitsInProperty_(text) {
  const bytes = Utilities.newBlob(text).getBytes().length;
  return bytes < 9 * 1024;  // under 9KB fits in one value
}

That leads to one principle: keep only a small index — which job, where, and when — in PropertiesService, and push the result body to a separate durable sink.

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
Reproduce how PropertiesService's 9KB-per-value and 500KB-total limits plus read-modify-write races silently overwrite a Gemini result
Size LockService.getScriptLock with a sensible waitLock budget and pick the right lock granularity, shown with the failure cases
Ship a complete Apps Script store that offloads large output to Drive and blocks double-apply with an idempotency key
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-06-29
Keeping Apps Script + Gemini Automations on Least Privilege: Explicit Scopes and Catching Scope Creep
Apps Script automations that call Gemini quietly accumulate OAuth scopes. Here is how to declare explicit scopes in appsscript.json, catch scope creep in CI, and avoid forcing every user to re-consent.
Workspace2026-04-09
Google Workspace × Gemini API Automation: Production Notes on 12 Apps Script Patterns
12 Gemini API + Apps Script patterns for Gmail, Docs, Sheets, and Calendar automation—plus the production snags I hit running this across four sites and an app support inbox: swallowed 429s, JSON code fences, the 6-minute cap, and flash-vs-pro routing, with measured numbers.
API / SDK2026-05-18
Why Your Apps Script Stops Mid-Batch When Calling the Gemini API — UrlFetchApp Timeouts and the 6-Minute Execution Limit
When Apps Script calls the Gemini API, two limits collide: UrlFetchApp's response timeout and the 6-minute script runtime cap. Here is how to tell them apart and how I work around them with chunking, checkpoints, and time-based triggers.
📚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 →