Preventing S3 Deployment Regressions: Hard Rules for Multi-Environment Deploys
Last session, a stale local index.html was deployed to production S3, wiping three previously-working features on queenofsandiego.com: the JADA → BOOK NOW hero crossfade, the Stripe embedded checkout flow, and accidentally resurrecting a deleted "Ranch & Coast readers..." hero line. The deployment also violated staging-first protocol by pushing both staging and prod in a single command. This post documents the root cause, the hard rules now in place to prevent it, and how to structure multi-environment deploys safely.
What Went Wrong
The failure chain was simple but preventable:
- No pre-flight diff: Local
index.htmlwas 3,650 lines and hadn't been pulled from S3 in days. Prod S3 was newer. - No staging gate: The same command deployed to both
s3://queenofsandiego.com/(prod) and staging in one shot, violating the staging-first rule already documented in prior session notes. - Ignored prior warnings: The previous session-summary explicitly flagged "stale local files" as a risk. The warning was visible but not enforced.
- No snapshot before overwrite: S3 has no versioning enabled; once the old file was gone, recovery required digging through Git history and Git LFS.
Three working features vanished. The hero fade, the Stripe checkout, and a deleted line all came back — a classic "deploy-and-discover" failure that should never reach production.
The Hard Rules (D1–D8): Now Enforced
To prevent this, eight non-negotiable rules are now baked into /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, loaded on every session:
- D1 — Pull and Diff Before Edit: Before touching any file that targets S3, fetch the current version from prod and diff it against local. Document what has changed and why.
- D2 — Staging-Only, Single-Target Deploys: Each deploy command targets one environment only. Staging first; prod only after CB review.
- D3 — One Logical Change Per Deploy: Don't batch unrelated edits. One feature change = one deploy cycle.
- D4 — Obey Prior Session Warnings: If a prior session-summary flags a risk, treat it as a blocking condition. Escalate before proceeding.
- D5 — Snapshot Prod Before Overwrite: Since S3 versioning is not enabled on this bucket, take a manual backup by copying the current prod file to a
_backups/prefix in the same bucket before anycporsync. - D6 — Print Proof Block Before Deploy: Before executing any S3 copy, print a six-line proof block to chat showing: file path, source hash, dest hash, timestamp, feature tokens being modified, and your name. Require explicit approval (e.g., "approved for staging" or "approved for prod").
- D7 — Feature-Token Registry: Maintain a regex-searchable list of feature identifiers in the file (e.g.,
jada-crossfade,stripe-embedded-checkout,hero-ranch-coast-line). Grep the current S3 prod against local before deploy. Log any tokens that disappear. - D8 — Escalate if S3 is Ahead: If the current prod file is newer than local (by modification time or hash), stop and escalate to CB. Do not overwrite.
Technical Implementation
These rules live in the QOS-specific CLAUDE.md file, which Claude loads at the start of every session for that repository. The rules are phrased as imperatives, not suggestions, and include specific command patterns.
Example pre-deploy workflow (now enforced):
# Rule D1: Pull and diff
aws s3 cp s3://queenofsandiego.com/index.html ./index.html.prod --region us-west-2
diff -u index.html.prod index.html | head -50
# Inspect feature tokens (Rule D7)
grep -E "jada-crossfade|stripe-embedded-checkout|hero-ranch-coast" index.html.prod
# Rule D6: Proof block (printed before any action)
echo "SOURCE: ./index.html (3650 lines, sha256: abc123...)"
echo "DEST: s3://queenofsandiego.com/index.html"
echo "FEATURES MODIFIED: jada-crossfade (active in local)"
echo "DEPLOY TARGET: staging only"
echo "TIME: 2025-01-15T14:32Z"
echo "OPERATOR: claude-sonnet-4-6"
# Require CB approval here
# Rule D5: Snapshot prod
aws s3 cp s3://queenofsandiego.com/index.html \
s3://queenofsandiego.com/_backups/index.html.2025-01-15-pre-deploy \
--region us-west-2
# Rule D2: Deploy to staging only
aws s3 cp ./index.html s3://queenofsandiego-staging.s3.us-west-2.amazonaws.com/index.html \
--region us-west-2 --cache-control "max-age=300"
# CloudFront invalidation for staging dist
aws cloudfront create-invalidation \
--distribution-id STAGING_DIST_ID \
--paths "/*" \
--region us-west-2
Why this structure: Staging gets a short TTL (300 sec); prod typically gets 3600. The snapshot goes to _backups/ with a timestamp, so if something goes wrong, CB can see what was deployed and when. Feature-token grep catches accidental deletions before they go live.
Infrastructure & File Paths
For queenofsandiego.com:
- Prod S3 bucket:
s3://queenofsandiego.com(us-west-2) - Staging bucket:
s3://queenofsandiego-staging.s3.us-west-2.amazonaws.com - Prod CloudFront dist:
PROD_DIST_ID(configured in Route53) - Staging CloudFront dist:
STAGING_DIST_ID - Index file:
/index.html(root object) - Backup prefix:
s3://queenofsandiego.com/_backups/(never served; for recovery only) - Feature tokens in index.html: Search for
jada-crossfade,stripe-embedded-checkout,hero-ranch-coast-line, and any other named sections
The CLAUDE.md file itself is at /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md