Preventing Deployment Regressions: Hard Rules for Multi-Target S3 Workflows
Over the last 3 hours, a development session on queenofsandiego.com deployed a stale local index.html to production S3, inadvertently wiping three features that had been working in the live environment: the JADA → BOOK NOW hero crossfade animation, the Stripe embedded checkout booking flow, and (unexpectedly) resurrected a deleted "Ranch & Coast readers..." hero line that had been intentionally removed in a prior session.
The root cause was straightforward: local file state was ahead of what the developer had last reviewed, but behind what was already live in S3. The deploy command copied the stale local version over the newer production state without validation, and the single-command deploy touched both staging and prod simultaneously, violating the staging-first gate that should have caught the regression.
This incident prompted a formal rule-set to prevent recurrence, codified in the project's agent instructions so it auto-loads on every session.
What Happened: The Regression Chain
- Local → S3 copy without diff:
/Users/cb/Documents/repos/sites/sailjada.com/index.htmlwas copied to both thestaging.sailjada.comand production CloudFront distributions without first pulling the current S3 state and diffing against it. - Simultaneous staging + prod deploy: A single command deployed to both targets in one operation, bypassing the manual review gate at staging.
- Missing prior-session context: The session summary from an earlier agent had explicitly warned about stale local files. This warning was present in the project memory but not re-surfaced during file edits.
- No S3 versioning fallback: CloudFront + S3 for this project do not use S3 object versioning, so the old production state was immediately overwritten with no recovery path short of git history.
Result: three distinct features vanished from production, requiring a rollback via CloudFront cache invalidation and re-deployment from git.
Technical Details: The Eight Hard Rules
To prevent this class of error, eight non-negotiable rules were encoded into /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, which is automatically loaded at the start of every session for that project:
D1: Always Pull + Diff Before Editing
Before modifying any file that will be deployed to S3, retrieve the current production state and compare it locally:
aws s3 cp s3://queenofsandiego-web-prod/index.html ./index.html.prod --profile sailjada
diff -u index.html.prod index.html
If the diff shows features you don't recognize or intend to change, stop and ask before proceeding.
D2: Single-Target, Single-Logical-Change Deploys
Never deploy to both staging and production in one command. Always deploy to staging.sailjada.com first (CloudFront dist ID: E... redacted), verify manually or via smoke tests, then promote to production (CloudFront dist ID: E... redacted) in a separate, gated command.
D3: One File Per Logical Change
Each git commit and each deploy should represent one coherent feature or fix. If you're touching index.html, styles.css, and gas-referral/Bootstrap.gs for separate reasons, deploy them separately. This makes rollbacks surgical and audit trails clear.
D4: Obey Your Own Prior Session Warnings
Every session summary that mentions risk (stale files, unverified S3 state, pending approvals) is a future agent's roadmap. If you see a warning in memory or in a CLAUDE.md note, re-check before deploying. If in doubt, re-read the session context and ask.
D5: Snapshot Production Before Overwrite
Since S3 versioning is not enabled on queenofsandiego-web-prod, create a manual backup before any cp` or sync that overwrites:
aws s3 cp s3://queenofsandiego-web-prod/index.html ./backups/index.html.$(date +%s) --profile sailjada
D6: Print a Six-Line Proof Block Before Every Deploy
Before running any CloudFront invalidation or S3 copy, output:
FILE: index.html | SIZE: XXX bytes | SHA256: ... | TARGETS: staging only | GIT: commit ABC | FEATURES MODIFIED: [hero fade, checkout link] | CONFIDENCE: ready
This forces explicit verification and leaves a record in the chat for auditing.
D7: Maintain a Feature Token Registry
Keep a list of named features and a grep-able token in the code for each. Example:
FEATURE: JADA_HERO_FADE | TOKEN: data-feature="jada-crossfade" | STATUS: live in prod
FEATURE: STRIPE_EMBEDDED_CHECKOUT | TOKEN: id="stripe-checkout-embed" | STATUS: live in prod
FEATURE: RANCH_COAST_HERO | TOKEN: id="ranch-coast-hero" | STATUS: deleted (do not resurrect)
Before deploying, grep the local file against this list to catch unexpected resurrections or deletions.
D8: Escalate to CB If S3 is Ahead
If aws s3 ls s3://queenofsandiego-web-prod/index.html shows a modification time newer than your local file's last git commit, do not deploy. Notify CB and ask for direction.
Infrastructure and Deployment Path
The deployment flow for sailjada.com projects is:
- Local git:
/Users/cb/Documents/repos/sites/sailjada.com/— source of truth for code. - S3 staging bucket:
sailjada-web-staging— development/review environment. CloudFront dist configured to serve from this bucket. - S3 production bucket:
queenofsandiego-web-prod(note: legacy naming, now also serves sailjada.com prod) — live customer-facing site. CloudFront dist with cache TTL 3600s. - CloudFront invalidation: After any S3 update, invalidate the distribution cache with
aws cloudfront create-invalidation --distribution-id E... --paths "/*"to ensure clients see the new version within ~60s. - Route53: DNS for
sailjada.comandstaging.sailjada.comare aliased to their respective CloudFront distributions in thesailjada.comzone.
Why These Rules Matter
Development on customer-facing sites has asymmetric risk: a single bad deploy is visible to users and requires either a quick rollback (if you have a snapshot) or a hot fix (fast but stressful). The eight rules trade