```html

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

Over the past three hours, a deployment incident on queenofsandiego.com wiped three working features—a hero image crossfade animation, an embedded Stripe checkout flow, and a previously-removed marketing line—by deploying a stale local index.html over a newer production version in S3. The root cause wasn't a code bug; it was a procedural gap. This post documents the incident, the infrastructure pattern that enabled it, and the eight hard rules we've implemented to prevent it happening again.

What Happened

The queenofsandiego.com site is a static site hosted on S3 (queenofsandiego.com bucket) behind a CloudFront distribution. Local development happens in /Users/cb/Documents/repos/sites/queenofsandiego.com/, with a 3,650-line index.html file containing hero animations, booking flows, and marketing copy.

The incident flow:

  • Local index.html was out of sync with S3 production (it lacked the hero JADA→BOOK NOW crossfade and the Stripe embedded checkout)
  • A deployment command copied local index.html to both staging and prod environments simultaneously
  • S3 prod was overwritten with the stale version, wiping the two working features
  • Additionally, a deleted marketing line ("For Ranch & Coast readers...") reappeared, confirming the older local file had been deployed
  • No snapshot of prod existed before overwrite, making recovery require manual reconstruction from git history

The deployment command pattern was:

cp index.html s3://queenofsandiego.com/staging/ && cp index.html s3://queenofsandiego.com/prod/

This violated two unwritten rules: (1) pull and diff S3 current before modifying local files, and (2) stage-only deploys, with manual promotion to prod after review.

Infrastructure and Deployment Pattern

The queenofsandiego.com infrastructure uses:

  • S3 bucket: queenofsandiego.com (us-west-1)
  • CloudFront distribution: primary distribution for www and apex domain routing
  • Route53: queenofsandiego.com zone with CNAME to CloudFront
  • Deployment targets: staging prefix and prod prefix in the same bucket (both served via CloudFront, staging used for pre-release testing)
  • No S3 versioning: bucket versioning was disabled, so overwrites are permanent without external snapshots

This architecture is common for low-latency static sites, but it creates a single point of failure: the local filesystem becomes the source of truth, rather than S3 being the authoritative copy. When a developer has an out-of-date local file, a careless deploy wipes production.

The Eight Hard Rules (D1–D8)

To prevent this class of regression, we've codified eight rules into /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, loaded automatically on every session:

D1: Pull and Diff Before Any Edit
Before modifying local index.html, pull the current version from S3 prod using the AWS CLI and diff against local:

aws s3 cp s3://queenofsandiego.com/index.html ./index.html.s3-prod
diff -u index.html.s3-prod index.html | head -50

If S3 is ahead of local, stop and escalate to CB. Never assume local is correct.

D2: Staging-Only Single-Target Deploys
Every deploy targets staging only, using a single command with a single target:

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

Never use && to chain multiple targets. Never deploy to both staging and prod in one command. Prod is promoted manually by CB after staging review.

D3: One File Per Logical Change
Each git commit and each deploy targets one file corresponding to one feature (e.g., index.html for hero animation, booking.js for Stripe flow). No multi-file deploys. This keeps change scope visible and rollback granular.

D4: Obey Prior Session Warnings
If a prior session summary warned about stale local files or S3 drift, treat that warning as a blocker. Re-read the prior summary before deploying. If the warning is still valid, pull fresh S3 state before proceeding.

D5: Snapshot Prod Before Overwrite
Before any deploy to prod, create a timestamped snapshot of the current prod version:

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

Store up to 10 recent snapshots. This is a stopgap until S3 versioning is enabled.

D6: Print a Six-Line Proof Block
Before executing any cp or upload command, print a proof block in chat showing:

  • Source file path (local) and size
  • Destination S3 path
  • Diff summary (number of lines changed, features touched)
  • Commit hash of local file
  • Current S3 prod hash (from ETag)
  • Approval status (waiting for CB or auto-approved via rule)

Wait for CB to say "deploy" or "hold" before executing.

D7: Feature-Token Registry
Maintain a FEATURE_TOKENS.md file listing feature identifiers and their current status (e.g., HERO_JADA_CROSSFADE: deployed in prod, STRIPE_EMBEDDED_CHECKOUT: deployed in prod). Grep the local index.html against these tokens before deploy. If a token is missing that should be present, stop and investigate.

D8: Escalate to CB if S3 is Ahead of Local
If the diff in D1 shows that S3 prod contains lines or features that local does not, do not proceed with deploy. Escalate to CB immediately with the diff output. This is a conflict state that requires human judgment (e.g., features deployed by a different agent, or manual S3 edits).

Implementation in CLAUDE.md

These rules are now stored in a [HARD RULES] section at the top of /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md and will be loaded into every session context. A condensed version has also been added to the top-level /Users