Diagnosing and Staging a Google Apps Script Deployment Outage: The 403/404 Reserve Widget Crisis
What Happened
On June 2, 2026, all eleven event-listing pages across sailjada.com and queenofsandiego.com stopped accepting reservations. The "Reserve" button—a Google Apps Script web app embedded as an iframe—returned either HTTP 403 (Forbidden) or 404 (Not Found). This was a total stoppage affecting every inbound booking funnel, yet silent to end users; they saw a broken button, not an error message.
Root Cause Analysis
The outage had two independent causes:
- Primary (10 event pages): Apps Script deployment
1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3-NMX_9v0lQJ5had its access control reverted to "Owner only" (or equivalent private), causing the public-facing endpointhttps://script.google.com/macros/s/AKfycbWe...44Pme8wCA/execto reject all unauthorized requests with 403. - Secondary (worship page): A separate deployment was entirely deleted, leaving the iframe pointing to a non-existent endpoint, returning 404.
Both issues appear to have been introduced during a prior session's re-authentication and tooling refresh. The Google OAuth tokens were refreshed, but deployment permissions were not verified before going live.
Investigation Workflow
The diagnosis followed a systematic reverse-engineering path:
- Identify the live endpoints: Inspected all 11 event pages' HTML to extract iframe
srcattributes. Found two distinct Apps Script project IDs being called. - Test HTTP status: Curled both endpoints:
curl -I https://script.google.com/macros/s/AKfycbWe...44Pme8wCA/exec # Returns 403 Forbidden curl -I https://script.google.com/macros/s/AKfycbWe...AFsLWaO3/exec # Returns 404 Not Found - Cross-reference Apps Script projects: Searched the source repository and ops notes for mentions of the project ID
1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3and found it referenced in deployment documentation. - Inspect
claspconfigurations: Located.clasp.jsonfiles in the source tree to confirm which local project maps to which Google Apps Script deployment. - Verify project accessibility: Authenticated to the Google Apps Script console and attempted to open the project. Confirmed it exists and is owned by the operations account, but the deployment had incorrect access settings.
Technical Details: The Fix (Staged, Not Yet Applied)
The fix is a single action in the Google Apps Script console, but understanding *why* it works requires clarity on Apps Script's deployment model:
- Project ID vs. Deployment ID: The Apps Script *project* is the source code container. A *deployment* is a versioned, published instance of that project with its own URL and access control. The URL format is
https://script.google.com/macros/s/{DEPLOYMENT_ID}/exec. - Access Control: Each deployment has an "Execute as" setting (who the code runs as) and a "Who has access" setting (who can invoke it). For a public reserve widget, access must be set to "Anyone."
- Current State: The deployment exists and is reachable (not deleted), but its "Who has access" is set to a private mode (likely "Me" or an explicit user list), causing the 403.
The staged fix:
- Open
script.google.com→ Project1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3-NMX_9v0lQJ5 - Navigate to Deploy → Manage deployments
- Click the active deployment (deployment ID ending in
44Pme8wCA) - Set "Who has access" to "Anyone"
- Set "Execute as" to "Me" (the ops account)
- Click Deploy
This change updates the deployment's access control *without* changing its URL. The pages do not need redeployment; the same /exec URL will suddenly return 200 and execute correctly.
Why This Approach Was Chosen
- Zero downtime for page code: The pages themselves are static (S3 + CloudFront). Changing the Apps Script deployment access does not require a page redeploy or cache invalidation.
- Minimal blast radius: Apps Script deployments are isolated units. Re-configuring one does not affect others or the hosting infrastructure.
- Atomic operation: Once "Anyone" access is set, the endpoint becomes live immediately. No intermediate states; the button works or it doesn't.
- Diagnosability: The HTTP 403 is a clear signal of an access control issue, not a code bug or transient error. Fixing access control is the natural solution.
The Secondary Issue: Worship Page 404
The worship page's endpoint returns 404, indicating the deployment has been deleted entirely. The fix for this is a redeploy from source:
- Locate the worship reserve widget source code (likely in
~/Documents/repos/tools/or a similar path). - Verify the
.clasp.jsonfile points to the correct Apps Script project ID. - Run
clasp pushto sync the source code to the project. - Run
clasp deployto create a new deployment, orclasp deploy --deploymentId {ID}to redeploy into the same URL slot if the ID is known. - If the old deployment ID is unknown, create a new deployment and update the worship page's iframe
srcto point to the new endpoint.
This requires coordinated updates: if a new deployment ID is generated, the worship page HTML must be regenerated and redeployed to S3 + CloudFront.
Infrastructure Context
- Event pages: Hosted on S3 (bucket structure:
queenofsandiego.com/events/) and distributed via CloudFront. Each page is a static HTML file with inline iframes pointing to the Apps Script endpoints. - Apps Script projects: Two independent Google Apps Script projects, each with one or more deployments. Deployments are the public-facing units.
- Dependency graph: Pages → CloudFront → S3 HTML → iframe src (Apps Script endpoint). Pages are decoupled from Apps Script