```html

Preventing S3 Deployment Regressions: Hard Rules for Stateful Frontend Codebases

Last week, a routine CloudFront invalidation and S3 deployment regressed three critical features on queenofsandiego.com by deploying a stale local index.html over a newer production version. The hero JADA→BOOK NOW crossfade animation vanished, the Stripe embedded checkout flow broke, and a previously-deleted "For Ranch & Coast readers..." hero line resurrected itself. This post documents the failure mode, the hard rules we implemented to prevent recurrence, and why stateful frontend deploys need explicit guardrails that code review alone cannot catch.

The Failure Mode

The regression occurred because:

  • Local environment was stale: The developer's working copy of /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html was behind production by multiple sessions. No pull-and-diff step preceded the edit.
  • Multi-target deploy in one command: CloudFront invalidation and S3 copy commands were chained together, pushing both staging and prod in parallel rather than validating staging first.
  • No proof-of-deployment block: Before overwriting S3, there was no printed diff snapshot or feature-token check in the chat history.
  • S3 versioning disabled: queenofsandiego.com bucket does not retain object versions, so the stale file overwrote the live one with no recovery path.
  • Ignored prior session warnings: The developer's own previous session-summary notes flagged stale local files as a known risk, but were not loaded at session start.

Infrastructure Context

The deployment pipeline uses:

  • S3 bucket: queenofsandiego.com (us-west-2, no versioning, public read access via CloudFront)
  • CloudFront distribution: managed by Terraform, configured to cache /index.html for 300 seconds
  • Local source: /Users/cb/Documents/repos/sites/queenofsandiego.com/ (git-tracked)
  • Deployment method: bash + AWS CLI: aws s3 cp --recursive --exclude "*" --include "*.html"
  • Invalidation: Manual CloudFront invalidation of /* (full distribution)

The codebase is 3,650 lines of single-file HTML + inline CSS/JS. Every deployment is a full overwrite of one critical asset.

The Hard Rules (D1–D8)

We encoded eight mandatory rules into /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, auto-loaded at every session start:

D1: Pull S3 and diff before any edit. Before modifying local index.html, fetch current production from S3 and compare:

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

If local is behind, incorporate prod changes into your working copy before touching the file. Document the merge in the chat.

D2: Staging-only, single-target deploys. Never deploy to both staging and prod in one command. Structure is:

# Stage 1: Deploy only to staging
aws s3 cp index.html s3://queenofsandiego.com/staging/index.html

# Stage 2: CB reviews staging.queenofsandiego.com live in browser

# Stage 3: Only after CB approval, promote to prod
aws s3 cp s3://queenofsandiego.com/staging/index.html s3://queenofsandiego.com/index.html
aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/*"

D3: One logical change per deployment. Do not batch unrelated feature edits. If you modify both the hero animation AND the Stripe integration, deploy them separately so a rollback can be surgical.

D4: Obey prior session summaries. Every session must begin by reading CLAUDE.md and the prior session's summary in the chat. If it flagged "stale local files," abort and re-pull.

D5: Snapshot prod before any cp. Before pushing to prod, print a six-line proof block:

---PROD SNAPSHOT BEFORE OVERWRITE---
File: index.html
S3 current (first 200 bytes):
[first 200 bytes of s3://queenofsandiego.com/index.html]
Local (first 200 bytes):
[first 200 bytes of /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html]
---END SNAPSHOT---

This creates an immutable record in chat for forensics.

D6: Maintain a feature-token registry. Create a file /Users/cb/Documents/repos/sites/queenofsandiego.com/FEATURE_TOKENS.md listing every active feature and a unique string grep target:

## Active Features (as of [DATE])

- JADA→BOOK NOW hero crossfade: `
` - Stripe embedded checkout: `` - Ranch & Coast hero line (DELETED): [was removed in session X] Before any prod deploy, grep local against S3 current to confirm all active tokens present.

D7: Escalate to CB if S3 is ahead. If the diff shows S3 has content local does not, stop immediately and message CB with the diff. Do not proceed without explicit approval.

D8: No manual edits to prod assets without git commit. Every source file must be committed to git with a descriptive message before any S3 push. This ensures reversibility and audit trail.

Key Decision: Why No Lambda or Version Control?

Enabling S3 object versioning on a production bucket serving 3,650-line HTML files would add storage cost (minimal) but more importantly increases mental overhead during incident response. The six-line snapshot block + feature-token registry are cheaper (zero marginal cost) and create an explicit, reviewable decision point before each prod push. They force the developer to think.

We chose staging-first validation over automated canary deployments because the audience is small (crew, potential guests), the blast radius of a regression is high (booking flow down = revenue loss), and human eyes on a 5-min staging check are more reliable than complex automation for a 3,650-line file.

Distribution to Other Sites

A condensed version of these rules (D1, D2, D5, D8) was added to the top-level /Users/cb/Documents/repos/CLAUDE.md so developers working on sailjada.com, 86from.io, and other static sites benefit from the pattern without full duplication.

What's Next

Pending blockers for the Keely referral booking