```html

Preventing S3 Deployment Regressions: Hard Rules for Multi-Environment CI/CD

Last week, a deployment to queenofsandiego.com wiped three working features by pushing a stale local index.html over a newer production version in S3. The root cause wasn't a tooling gap—it was a process gap. This post documents the eight hard rules we've now embedded into our deployment workflow, why each one matters, and how to implement them in your own multi-environment setups.

What Went Wrong

A single cp command deployed a local build artifact to both staging and production simultaneously, bypassing the staging-first validation gate. The local file was 6 hours stale relative to what was already live in production S3. Three features regressed:

  • JADA hero section crossfade animation (working locally, but not in the live copy)
  • Stripe embedded checkout booking flow (removed from staging 2 hours prior, but local version still had old code)
  • A hero line ("For Ranch & Coast readers...") that had been intentionally deleted weeks earlier

The deployment command ignored a session-summary warning from the prior agent interaction that explicitly flagged "stale local files." S3 versioning wasn't enabled, so there was no rollback point.

The Eight Hard Rules (D1–D8)

We've encoded these into /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, which loads at the start of every session for that site:

D1: Pull S3 and Diff Before Edit

Why: Your local working directory can drift from production without you knowing. S3 is the source of truth.

Implementation:

aws s3 sync s3://queenofsandiego.com/prod/ ./s3-prod-snapshot/ --exclude "*" --include "*.html" --include "*.css" --include "*.js"
diff -u ./s3-prod-snapshot/index.html ./index.html | head -50

Before touching any file, run this. If the diff shows changes you didn't make, your local is stale. Rebase against S3 before proceeding.

D2: Staging-Only, Single-Target Deploys

Why: Deploying to both staging and production in one command removes the human review gate.

Implementation:

# Correct: staging first, one target
aws s3 cp index.html s3://staging.queenofsandiego.com/index.html --cache-control "max-age=0"

# Wrong: both targets in one command
aws s3 cp index.html s3://queenofsandiego.com/prod/ --recursive

Always stage first. Always wait for CB review before promoting to production. Never use --recursive without explicitly listing the source and target files.

D3: One File Per Logical Change

Why: If you deploy index.html and styles.css in the same push, and the page breaks, you can't isolate the cause.

Implementation:

# Commit message: "Hero crossfade timing fix"
# Deploy only: index.html

# Separate commit/deploy: "Stripe checkout flow update"
# Deploy only: checkout.js

One file per deployment. If you need to change multiple files for a single feature, deploy them sequentially and validate between each push.

D4: Obey Prior Session Summaries

Why: Session summaries exist to warn future agents (including future versions of you) about known risks.

Implementation:

If a prior session ended with "⚠️ WARNING: Local index.html is 6 hours stale," treat that as a blocking issue. Pull S3, compare, and rebase before editing. Don't assume the warning was noise.

D5: Snapshot Production Before Overwrite

Why: S3 versioning isn't enabled on our buckets. Your snapshot is your rollback.

Implementation:

TIMESTAMP=$(date +%Y%m%d-%H%M%S)
aws s3 cp s3://queenofsandiego.com/prod/index.html ./backups/index.html.${TIMESTAMP}.bak
# ... make changes ...
aws s3 cp index.html s3://queenofsandiego.com/prod/index.html

Before deploying to production, always download the current version to ./backups/ with a timestamp. If the deploy breaks something, you have an instant rollback.

D6: Print a Six-Line Proof Block

Why: Last-minute validation catches mistakes before they go live.

Implementation:

Before running any cp or aws s3 command, print this block in chat:


=== DEPLOYMENT PROOF ===
SOURCE: ./index.html
TARGET: s3://staging.queenofsandiego.com/index.html
SIZE: 127 KB
MD5: a3f8c2d9e1b4...
FEATURE_CHANGES: Hero crossfade timing (line 245)
APPROVAL: pending CB review
=== END PROOF ===

Doing this forces a pause and lets you catch copy-paste errors or wrong bucket names.

D7: Maintain a Feature-Token Registry

Why: You need a fast way to verify that your deployment didn't erase working features.

Implementation:

Add a FEATURE_TOKENS.md file in your repo root:

## Feature Tokens (QOS)

- `HERO_JADA_CROSSFADE`: data-animate="jada-book-crossfade" (line ~245 in index.html)
- `STRIPE_EMBEDDED_CHECKOUT`: <script src="https://js.stripe.com/v3"> (line ~680)
- `BOOKING_FLOW_FORM`: id="booking-form-container" (line ~520)

After each deployment, grep production for these strings:
```
aws s3 cp s3://queenofsandiego.com/prod/index.html - | grep -c "HERO_JADA_CROSSFADE"
```
Should return 1 (or more). If it returns 0, rollback immediately.

After deploying to production, run:

aws s3 cp s3://queenofsandiego.com/prod/index.html ./verify.html
grep -c "HERO_JADA_CROSSFADE" ./verify.html  # Should be ≥ 1
grep -c "STRIPE_EMBEDDED_CHECKOUT" ./verify.html  # Should be ≥ 1

D8: Escalate to CB If S3 is Ahead of Local

Why: It means production was updated outside your session (maybe CB pushed a fix directly, or another agent deployed). You need human context before overwriting it.

Implementation:

If your diff shows