Preventing Deployment Regressions: Hard Rules for S3-Backed Static Sites
Over the last 3 hours, a session regressed three distinct features on queenofsandiego.com by deploying a stale local index.html over a newer S3 production version. The incident wiped the working hero JADA → BOOK NOW crossfade animation, the Stripe embedded checkout booking flow, and inadvertently resurrected a deleted "For Ranch & Coast readers..." hero line that had been intentionally removed weeks prior.
This post documents the root cause, the eight hard rules we've codified to prevent recurrence, and the infrastructure patterns that make those rules enforceable.
What Happened
- Local state was stale: The developer's working directory at
/Users/cb/Documents/repos/sites/queenofsandiego.com/index.htmlwas several hours behind the current S3 production version ins3://queenofsandiego.com/index.html. - No pre-deploy diff: The session did not pull the current S3 version and diff it against the local file before issuing the copy command.
- Single command deployed both staging and prod: Instead of deploying to staging first, waiting for review, and promoting in a separate step, both targets were overwritten in the same AWS CLI invocation.
- Ignored prior session warnings: The previous session's summary explicitly warned about stale local files and the risk of deployment-time data loss. This warning was not escalated or checked.
- No proof block in chat: No human-readable diff or feature inventory was printed to the chat before the
aws s3 cpcommand executed.
Technical Details: The Eight Hard Rules (D1–D8)
We have encoded these rules into /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, which is automatically loaded at the top of every QOS session. They are:
D1: Pull S3 and diff before editing any tracked file.
Before modifying index.html, fetch the live version from S3 and run diff -u against local. This exposes any drift immediately.
aws s3 cp s3://queenofsandiego.com/index.html ./index.html.prod
diff -u index.html.prod index.html > /tmp/local_vs_prod.diff
D2: Staging-only, single-target deploys.
Never deploy to both staging and prod in one command. Deploy to staging first, print the command and wait for human review, then promote in a separate step.
# STAGING ONLY
aws s3 cp index.html s3://staging.queenofsandiego.com/index.html --cache-control "max-age=0, no-cache"
# PROD (only after human approval)
aws s3 cp index.html s3://queenofsandiego.com/index.html --cache-control "max-age=3600"
D3: One logical change per deploy.
Each production deploy should touch a single feature, section, or bug fix. If a session changes the hero, the booking form, and the footer in one go, it must be three separate deploys to three separate staging targets.
D4: Obey your own prior session-summary warnings.
If a previous session's CLAUDE.md summary flags stale local files, S3 drift, or deployment risks, escalate that concern to the human before proceeding. Do not override it silently.
D5: Snapshot prod before overwriting S3.
Because S3 versioning is not enabled on queenofsandiego.com's bucket, any cp command is irreversible. Before overwriting, copy the current prod version to a timestamped backup in the same bucket:
aws s3 cp s3://queenofsandiego.com/index.html s3://queenofsandiego.com/backups/index.html.$(date +%Y%m%d-%H%M%S)
D6: Print a six-line proof block in chat before any cp.
Before executing any S3 copy, print a human-readable summary in the chat that includes:
- Source file path and last-modified timestamp
- Target bucket and object key
- CloudFront distribution ID affected
- A line count of the source file
- The three to five most significant changes (hero, booking form, footer, etc.)
- The estimated impact on end users
D7: Maintain a feature-token registry.
Create a file at /Users/cb/Documents/repos/sites/queenofsandiego.com/FEATURE_TOKENS.md that lists every major interactive or CSS feature, along with a unique grep-friendly token in the HTML. For example:
- JADA_HERO_CROSSFADE: `<div class="jada-hero" id="hero-crossfade">`
- STRIPE_EMBEDDED_CHECKOUT: `<script src="https://js.stripe.com/v3/pricing-table.js">`
- RANCH_COAST_HERO: `For Ranch & Coast readers...`
Before any prod deploy, grep the local file and the current S3 version for all tokens. If any token is present in S3 but absent in local, halt and escalate.
grep -c "hero-crossfade" index.html.prod # Must be > 0
grep -c "STRIPE_EMBEDDED_CHECKOUT" index.html # Must match prod
D8: Escalate to CB if S3 is ahead of local.
If the diff in D1 shows that S3 contains features, changes, or timestamps newer than local, do not proceed. Instead, print the full diff and ask CB whether to (A) rebase local, (B) deploy local anyway, or (C) abort and investigate.
Infrastructure Context
S3 bucket: queenofsandiego.com
CloudFront distribution: Points to the S3 origin; distribution ID is used for cache invalidation after each prod deploy.
Staging bucket: staging.queenofsandiego.com (separate, lower cache-control, not behind CloudFront CDN).
Backups location: s3://queenofsandiego.com/backups/ (same bucket, separate prefix).
No versioning is currently enabled on the production bucket. This is a deliberate trade-off: versioning would add storage cost and complexity, but it would also allow point-in-time recovery. The hard rules (D5–D7) are the compensating control.
Why These Rules Work
Layered defense: No single rule prevents all regressions. D1 catches drift; D4 catches ignored warnings; D5 enables recovery; D6 makes errors visible to humans before they propagate; D7 detects feature loss automatically; D8 escalates ambiguity.
Automation-friendly: Each rule is checkable by a script or Claude session without human judgment. They are not