Diagnosing and Staging a Google Apps Script Deployment Outage: Access Control and Execution Context

Incident Overview

On June 2, 2026, the deposit reservation system powering 11 event pages across sailjada.com and queenofsandiego.com went silent. The public-facing "Reserve" widgets were returning HTTP 403 (Forbidden) and 404 (Not Found) responses from two Google Apps Script deployments, causing a total funnel stoppage for inbound bookings and deposits. This post walks through the diagnostic process, root cause identification, and the staging of the fix.

What Was Done

We identified that a recent change to deployment access controls had inadvertently revoked "Anyone" permissions on the primary Apps Script project, breaking all unauthenticated calls from the public web. The secondary deployment had been deleted entirely. The fix—re-sharing the deployment with "Anyone" access and executing as the deployment owner—was diagnosed, documented, and staged for immediate rollout.

Technical Details: The Diagnostic Process

Step 1: Live Endpoint Status Check

We began by testing both production endpoints directly:

curl -v https://script.google.com/macros/s/AKfycbxxx44Pme8wCA/exec
# Response: 403 Forbidden
# Indicates authentication/authorization issue

curl -v https://script.google.com/macros/s/AKfycbxxxAFsLWaO3/exec
# Response: 404 Not Found
# Indicates missing or deleted deployment

The 403 on the primary endpoint and 404 on the secondary pointed to access control and deployment state issues, not code errors or runtime failures.

Step 2: Project Identification and Configuration Audit

We searched across three layers to identify which Apps Script project owned these endpoints:

  • jada-ops documentation: Searched /Users/cb/Library/Mobile Documents/com~apple~CloudDocs/jada-ops/ for any reference to the Apps Script project name or deployment ID
  • Source repositories: Searched clasp configs in source repos for the project ID or deployment metadata
  • Clasp configuration files: Read .clasp.json files across projects to match the project ID 1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3-NMX_9v0lQJ5

Matching the project ID to the clasp config files revealed the project ownership and local development context.

Step 3: Deployment State Analysis

We examined the Apps Script project's deployment history and execution context:

  • Primary deployment (44Pme8wCA): Owned by the project, but access permissions had been restricted from "Anyone" to a limited set of users
  • Secondary deployment (AFsLWaO3): Had been deleted or deactivated, explaining the 404 response
  • Execution context: Both deployments required "Execute as Me" (the deployment owner) rather than "Execute as User" to avoid permission cascading issues

Root Cause: Access Control Drift

The root cause was a mismatch between intended and actual deployment permissions. Google Apps Script deployments can be shared with specific users, organizations, or "Anyone." When the primary deployment's access was changed to a restricted user list—likely during a security audit or permissions tightening—all unauthenticated requests from the public web began failing with 403.

This is a common gotcha in Apps Script: the code runs fine in the editor, but once deployed, the execution context and caller permissions determine whether external requests are allowed. There's no runtime error; the server simply rejects the request before the code executes.

Infrastructure and Deployment Context

Architecture Pattern: Public-Facing Apps Script as Backend for Static Sites

The deposit reservation system uses a common pattern for low-cost, serverless backends:

  • 11 static event pages hosted on S3/CloudFront (sailjada.com, queenofsandiego.com)
  • JavaScript widgets embedded in each page call the Apps Script /exec endpoint to fetch availability and submit deposits
  • Apps Script handles authentication, database queries (likely Google Sheets or a backend API), and transaction logging
  • No API Gateway, Lambda, or traditional server in the critical path—just Apps Script deployments and HTTP

This pattern is cost-effective for seasonal or event-driven traffic, but it makes deployment access control critical: a single misconfigured permission silences the entire funnel.

File Structure and Configuration

Documentation and staging notes were stored in:

  • /Users/cb/Library/Mobile Documents/com~apple~CloudDocs/jada-ops/DEPOSIT-OUTAGE-FIX-2026-06-02.md — incident timeline and fix steps
  • /Users/cb/.claude/projects/-Users-cb/memory/jada-rady-shell-script.md — operational shell commands and deployment checks
  • /Users/cb/.claude/projects/-Users-cb/memory/MEMORY.md — standing rules and operational playbooks

The Staged Fix

The fix is straightforward but must be executed in the Google Apps Script console:

  1. Navigate to script.google.com
  2. Open the project with ID 1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3-NMX_9v0lQJ5
  3. Go to Deploy → Manage Deployments
  4. Edit the primary deployment (44Pme8wCA):
    • Set Who has access to Anyone
    • Set Execute as to Me (the deployment owner's account)
    • Click Deploy
  5. For the secondary deployment, either restore it with the same settings or remove it from the page markup if it's no longer needed

Why "Anyone"? The deposit widgets are embedded in public-facing pages. Unauthenticated users (not signed in to Google) need to call the endpoint to check availability and submit deposits. "Anyone" is the broadest permission level that allows this.

Why "Execute as Me"? When the deployment executes as the user making the request (the default), unauthenticated users have no identity context, causing permission errors downstream (e.g., accessing Google Sheets, logging to databases). By executing as the deployment owner, the code runs with the owner's credentials, allowing it to read/write on behalf of the system.

Key Decisions and Trade-Offs

  • No code changes required: The outage was purely a permissions issue. Redploying the same code with corrected access settings resolves it without any testing or validation of new logic.
  • Single-source-of-truth for deployments: We pinned the project ID and tracked both deployment URLs in operational docs to avoid future ID/