Your /exec URL Must Not Change on Every Redeploy
Reuse the deployment ID when you push a new version with clasp and the /exec URL stays fixed. Guard shared sheet writes with LockService.
Redeploy by reusing the deployment ID so the /exec URL stays fixed. If the web app address changes every time you create a new deployment, customer-saved links and external webhook integrations break that day.
# Bad: a new deployment every time -> a new URL
clasp deploy
# Good: reuse the same deployment ID -> URL stays fixed
clasp deploy -i <deploymentId> -d "v12 $(git rev-parse --short HEAD)"
Why it matters
A deploy URL is a contracted interface. Customer bookmarks, QR codes, and other systems’ webhooks point at that address. If it changes on every redeploy, you have to re-announce every integration: and a missed one becomes a “it worked yesterday” outage.
push and deploy are different
clasp pushoverwrites the HEAD of the Apps Script project with your current code. That’s “save,” not “deploy.”- Users run a version-pinned deployment, not HEAD.
- So do dev verification on the
/devURL (always the latest saved code), and delivery/ops smoke tests on the version-pinned/execURL.
clasp push overwrites the entire remote. If someone edited a file in the web editor, it can be lost: check remote changes before redeploying.
Concurrent writes to the same asset need LockService
When several users or triggers write to the same sheet at once, numbers collide or rows get overwritten. Wrap read-modify-write operations (append, number issuance, status change) in a lock.
function appendWithLock(row) {
const lock = LockService.getScriptLock();
if (!lock.tryLock(10000)) throw new Error("could not acquire lock");
try {
const sheet = SpreadsheetApp.getActive().getSheetByName("AuditLog");
sheet.appendRow(row);
SpreadsheetApp.flush(); // commit before release
} finally {
lock.releaseLock(); // always in finally
}
}
Rules: prefer tryLock(timeout), and always put releaseLock() in finally. Call SpreadsheetApp.flush() to commit the write before releasing the lock, so the next run doesn’t read a stale value. Don’t make network calls or run long computations inside the lock.
Deeper: the concurrency ceiling
Concurrent executions cap at about 30 per user. Counting LockService waiting, design for roughly 60 lossless concurrent users; beyond that, consider an external DB or a queue.
One line to keep: pin the address with the deployment ID, and protect shared writes with lock + flush.
Frequently asked questions
- Why does the web app URL change every time I run clasp deploy?
- Running the command without options creates a new deployment each time, which issues a new URL. Pass the -i flag with an existing deployment ID to reuse it and keep the /exec URL fixed.
- What is the difference between clasp push and clasp deploy?
- clasp push saves your code to the HEAD of the Apps Script project but does not create a new deployment. Users run a version-pinned deployment, so production changes require clasp deploy as well.
- How should LockService be used when multiple users write to the same sheet?
- Acquire the lock with tryLock(timeout), commit the write with SpreadsheetApp.flush() before releasing, and always call releaseLock() in a finally block. Avoid network calls or long computations inside the lock.