Preventing S3 Deployment Regressions: Hard Rules for Stateful Front-End Deploys
Over the last 3 hours, a Claude session inadvertently regressed three production features on queenofsandiego.com by deploying a stale local index.html over a newer S3 production version. The incident wiped the working JADA → BOOK NOW hero crossfade animation, the Stripe embedded checkout booking flow, and resurrected a previously-deleted "For Ranch & Coast readers..." hero line. This post documents the failure mode, root causes, and the hard rules we've now encoded to prevent it.
The Incident: What Broke
- Hero animation regression: The JADA → BOOK NOW CSS crossfade fade (implemented via
@keyframes heroFadein the 3,650-lineindex.html) was lost, replaced by static text. - Stripe checkout flow loss: The embedded Stripe Payment Element initialization (lines ~2840–2860) was missing, breaking the entire booking flow.
- Deleted content resurrection: A hero line targeting "Ranch & Coast readers" that had been intentionally removed in a prior session reappeared in production.
- Dual-environment deployment violation: Both
stagingandprodCloudFront distributions were invalidated in a single command, violating the staging-first deployment rule documented in session notes.
Root Cause Analysis
The session executed this command sequence without pulling the current S3 production state first:
# ❌ WRONG: Deploys local version over unknown remote state
cp /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html \
s3://queenofsandiego.com/index.html
aws cloudfront create-invalidation \
--distribution-id E1234ABCD5678 \
--paths "/*"
The local file was 2+ commits behind the S3 production version. Because the session didn't diff S3-current against local-current before editing, it had no visibility into what would be overwritten. The prior session summary explicitly warned about stale local files, but the warning was not re-read or honored.
Hard Rules for Safe Deploys (D1–D8)
We've now encoded eight mandatory checks into /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, which auto-loads at the start of every QOS session:
D1: Pull S3 and Diff Before Any Edit
# Before touching index.html, pull current S3 version
aws s3 cp s3://queenofsandiego.com/index.html ./index.html.prod
# Diff local against remote
diff -u index.html.prod index.html | head -100
If S3 has changes not in local, do not proceed until you understand what they are. If you cannot explain the diff, escalate to CB.
D2: Staging-Only Deploys (Single Target)
Every deploy targets exactly one environment. Deploy to staging first, never both staging and prod in a single command sequence. The staging S3 bucket is staging.queenofsandiego.com; prod is queenofsandiego.com. CloudFront distribution IDs are:
staging.queenofsandiego.com:E[STAGING_ID]queenofsandiego.com:E[PROD_ID](do not use in the same session as staging)
D3: One Logical Change Per Deploy
Each S3 upload and CloudFront invalidation represents one atomic feature or fix. Do not bundle unrelated changes. If you edit the hero animation, the booking form, and the footer in one session, that is three separate deploys (staging → review → prod for each).
D4: Obey Your Own Prior Session Warnings
Before editing any file, read the prior session summary in the project's CLAUDE.md. If it flags "stale local files," "S3 ahead of local," or "do not deploy without CB approval," that instruction is still binding. Do not override it in the same or immediately following session without explicit user re-confirmation.
D5: Snapshot Prod Before Overwriting
S3 does not have versioning enabled on queenofsandiego.com. Before any prod deploy, manually snapshot the current state:
# Save prod version with a timestamp
aws s3 cp s3://queenofsandiego.com/index.html \
s3://queenofsandiego.com/snapshots/index.html.$(date +%s).backup
Include the snapshot URL in your deploy confirmation message to CB.
D6: Print a Six-Line Proof Block Before Any cp
Immediately before executing an S3 copy command, print to chat:
═══ DEPLOY PROOF BLOCK ═══
Environment: staging | File: index.html | Change: [describe in one sentence]
Local hash: [first 8 chars of md5 of local file]
Remote hash: [first 8 chars of md5 of S3 current]
Staging invalidation dist: E[STAGING_ID]
Prod: NOT targeted in this session ✓
═══════════════════════════
If you cannot fill this block accurately, do not deploy.
D7: Maintain a Feature-Token Registry
In sites/queenofsandiego.com/FEATURE_TOKENS.md, list every dynamic feature in index.html with a unique grep-able token:
## Feature Tokens (grep S3-current against this to verify integrity)
- HERO_FADE: @keyframes heroFade { ... } — validates JADA→BOOK NOW animation
- STRIPE_CHECKOUT: const stripe = Stripe(...) — validates embedded payment element
- RANCH_COAST_HERO: "For Ranch & Coast readers" — should NOT appear in prod (deleted)
- BOOKING_FORM_V2: data-form-version="2" — validates latest form schema
After every prod deploy, grep the S3 version for each token and confirm presence/absence in chat.
D8: Escalate if S3 Is Ahead of Local
If your diff shows S3 has commits or changes not in your local clone, do not edit that file. Instead:
- Fetch the git remote:
git fetch origin - Check if the remote branch is ahead:
git log --oneline local..origin/main | head -10 - If so, pull and rebase:
git pull --rebase origin main - If the rebase has conflicts or the S3 state cannot be explained, message CB with the diff and wait for direction.
Infrastructure Details (No Secrets)
For context, here are the exact resource names involved (no credentials included):
- S3 buckets:
queenofsandiego.com