```html

Preventing S3 Deployment Regressions: Hard Rules for Multi-Environment Static Sites

Over the last 3 hours, a deployment to queenofsandiego.com wiped three features from production by deploying a stale local index.html over a newer S3 version. This post documents the failure mode, the hard rules we've now codified to prevent it, and the infrastructure patterns that caught it.

What Went Wrong

The regression deleted:

  • The hero JADA → BOOK NOW crossfade animation (CSS transitions + JS event binding)
  • The Stripe embedded checkout booking flow (form state, session creation, redirect logic)
  • A previously-removed "For Ranch & Coast readers..." hero text block (that had been intentionally deleted)

Root cause: the agent deployed from a local copy of /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html that was behind the live S3 version in s3://queenofsandiego.com/index.html. The deployment command overwrote prod without checking for drift or validating that local was current.

Secondary failure: the same command deployed both staging and prod in a single operation, violating the staging-first rule that was already documented. Neither the local code nor the deployment logic caught this.

The Eight Hard Rules (D1–D8)

We've encoded these into /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, where they auto-load on every session:

D1: Pull and Diff Before Edit

Before modifying any index file, pull the current S3 version into a staging buffer and diff it against local:

aws s3 cp s3://queenofsandiego.com/index.html ./index.html.s3-current
diff -u ./index.html.s3-current ./index.html | head -100

Why: Detects drift instantly. If S3 is ahead, you know not to overwrite.

D2: Staging-Only, Single-Target Deploys

Every deploy command names exactly one target environment:

aws s3 cp index.html s3://staging-queenofsandiego.com/index.html

Never: deploy to multiple environments in one command. Never use a script that infers targets.

D3: One Logical Change Per Commit

Each file edit should address a single feature or bug. If you're adding the Stripe checkout AND fixing CSS, that's two commits:

git add index.html && git commit -m "Add Stripe embedded checkout form"
git add index.html && git commit -m "Fix hero crossfade timing"

Why: Isolates regressions and makes rollbacks surgical.

D4: Obey Your Own Prior Session Warnings

If a previous session summary says "local index.html is stale" or "S3 is ahead," treat that as a blocking warning. Escalate to CB before proceeding.

D5: Snapshot Prod Before Overwrite (No S3 Versioning)

Since s3://queenofsandiego.com doesn't have versioning enabled, take a dated backup:

aws s3 cp s3://queenofsandiego.com/index.html ./backups/index.html.$(date +%s).bak

Why: Enables quick rollback if a deploy breaks production.

D6: Proof Block Before Deploy

Print a six-line block in chat before any cp or sync command:


=== DEPLOYMENT PROOF BLOCK ===
SOURCE:      ./index.html (local file size: 3,650 lines, checksum: abc123...)
TARGET:      s3://queenofsandiego.com/index.html (staging|prod)
ENVIRONMENT: staging (NOT PROD)
FEATURES:    Stripe embedded checkout, hero crossfade, guest count field
ROLLED_BACK: None (first deploy of this changeset)
APPROVAL:    Waiting for CB sign-off
=== END PROOF ===

D7: Feature Token Registry

Maintain a grep-able list of critical feature markers in the index.html header:



Before deploying to prod, verify each token is present in the current S3 version:

grep "FEATURE_TOKEN" s3://queenofsandiego.com/index.html | wc -l
# Expected: 3. If less, S3 is regressed — do not overwrite.

D8: Escalate to CB When S3 Is Ahead

If the diff shows S3 has code not in local, stop immediately and message CB with:

  • The diff output (first 50 lines)
  • The git log of the last 3 commits to index.html
  • A proposal: merge S3 into local, or discard local and pull S3?

Infrastructure & Deployment Context

queenofsandiego.com uses:

  • S3 Bucket: s3://queenofsandiego.com (static hosting, no versioning)
  • CloudFront Distribution: (ID masked for security) with origin pointing to the S3 bucket
  • Local Git Repo: /Users/cb/Documents/repos/sites/queenofsandiego.com/
  • Staging Bucket: s3://staging-queenofsandiego.com (separate CloudFront dist)

The lesson: static site deployments are not transactional by default. Without versioning or a pre-flight validation step, a stale local file can silently overwrite live code. The eight rules force that validation into the workflow before the risk window opens.

Key Decisions

Why not use CloudFormation or IaC for index.html? The file is 3,650 lines of hand-tuned HTML/CSS/JS for a sales funnel. Versioning it in IaC adds overhead; the cost of a single regression (feature loss + manual rebuild) is much higher than the cost of a strict manual pre-flight checklist.

Why feature tokens instead of just checksums? Checksums catch any change, but they don't tell you what broke. Feature tokens let you grep for the specific functionality you're protecting, making the regression visible in chat before you touch S3.

Why separate staging and prod buckets? It enforces the "one environment per deploy" rule at the infrastructure level. You cannot accidentally deploy to both in a single command.