Preventing S3 Deployment Regressions: Hard Rules for Multi-Environment Sailing Site Workflows
Over a three-hour development session, a regression incident on queenofsandiego.com wiped three production features by deploying a stale local index.html over a newer S3 prod version. The hero crossfade (JADA → BOOK NOW), the Stripe embedded checkout flow, and prior deletions all reverted. This post documents the root cause, the eight hard rules now enforced, and why they matter for multi-environment static site workflows.
The Incident: What Happened
The deployment command deployed both staging and prod in a single invocation, using a local copy of index.html that was out of sync with production. Because the local file lacked recent hero and Stripe changes, the S3 prod bucket received an older version, erasing live features. The session's own prior warning—logged in memory—about stale local files was overlooked.
- Feature lost: Hero JADA crossfade animation triggering the "BOOK NOW" call-to-action
- Feature lost: Stripe embedded checkout form (Session initialization + client-side token handling)
- Unintended revert: "For Ranch & Coast readers..." hero line, previously deleted, reappeared
- Root cause: No pre-deployment diff between local and S3; no staging-only gate; no snapshot before overwrite
Infrastructure Context
The sailjada.com and queenofsandiego.com deployments use a static-site architecture:
- S3 buckets:
queenofsandiego.com(prod) andstaging.queenofsandiego.com(staging) - CloudFront distributions: One per environment (prod ID, staging ID), with cache invalidation required post-deploy
- Index file:
/Users/cb/Documents/repos/sites/queenofsandiego.com/index.html(3,650 lines, contains hero, Stripe, booking flow) - Deployment method: Local CLI using
aws s3 cpwith recursive options - No versioning: S3 versioning is disabled; overwrites are permanent unless manually recovered from git or CloudFront logs
The Eight Hard Rules (D1–D8)
These rules are now auto-loaded from /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md at the start of every session:
D1: Pull and Diff Before Edit
Before touching index.html locally, always sync from S3:
aws s3 cp s3://queenofsandiego.com/index.html ./index.html --region us-west-2
Then diff local against S3 to see what's live:
diff -u <(aws s3 cp s3://queenofsandiego.com/index.html - --region us-west-2) ./index.html | head -50
Why: Prevents deploying over newer prod code. CloudFront logs can show what's cached, but S3 is the source of truth.
D2: Staging-Only Deploys (Single Target, Never Both)
Every edit must hit staging.queenofsandiego.com first, in isolation:
aws s3 cp index.html s3://staging.queenofsandiego.com/index.html --region us-west-2
Only after staging review is approved do you promote to prod. Never deploy staging and prod in the same command.
Why: A broken staging push can be fixed in minutes; a broken prod push affects live users and requires manual S3 recovery.
D3: One Logical Change Per Deploy
Each deploy should address one feature or fix. Multiple unrelated changes in a single push make it impossible to roll back granularly. If you edit the hero crossfade and the Stripe Session call in the same session, that's two deployments (staging test → prod for crossfade, then staging test → prod for Stripe).
Why: If the Stripe change breaks bookings but the hero fade is fine, you can't promote just the hero without the broken Stripe code.
D4: Obey Your Own Prior Session-Summary Warnings
Memory files (in /Users/cb/.claude/projects/memory/) and session summaries often include "do not deploy a stale local file" or "S3 is ahead of local" warnings. If your prior session flagged a risk, escalate to CB rather than deploying blind.
Why: You have context from prior work; ignoring your own notes leads to preventable regressions.
D5: Snapshot Prod Before Overwriting
Because S3 versioning is disabled, create a manual backup before cp:
aws s3 cp s3://queenofsandiego.com/index.html s3://queenofsandiego.com/index.html.backup.$(date +%s) --region us-west-2
Keep backups for 24 hours; older ones can be removed.
Why: If a deploy goes wrong, you can recover the prior version immediately without waiting for git archaeology or CB manual recovery.
D6: Print a Six-Line Proof Block Before Any cp Command
Before deploying, print to chat:
DEPLOY PROOF:
- Target bucket: s3://staging.queenofsandiego.com (or prod)
- Source file: index.html (date-checked vs S3)
- Change summary: [one sentence]
- Staging approved: [yes/no/date-timestamp]
- CloudFront dist: [dist-id]
- Rollback plan: [revert hash or backup timestamp]
Why: Forces explicit review before the point of no return. Catches typos (wrong bucket) and incomplete checks (forgot to test on staging).
D7: Maintain a Feature-Token Registry
Create a file (e.g., FEATURE_TOKENS.md) listing every major interactive element in index.html:
HERO_JADA_FADE— crossfade animation in hero sectionSTRIPE_CHECKOUT_FORM— embedded checkout session initializationBOOKING_REFERRAL_INPUT— referral code entry fieldRANCH_COAST_HERO_LINE— "For Ranch & Coast readers..." text (status: deleted as of [date])
grep -c "HERO_JADA_FADE" index.htmlWhy: Catches silent regressions where code is present but disabled, or where an old backup overwrites new code.
D8: Escalate to CB When S3 is Ahead of Local
If your diff shows S3