```html

Preventing S3 Deployment Regressions: A Case Study in Stateless Infrastructure Safeguards

Last session, a deployment to S3 wiped three working features on queenofsandiego.com by uploading a stale local index.html that was older than what already existed in production. The incident—overwriting a live Stripe embedded checkout, a CSS hero fade animation, and inadvertently resurrecting a deleted editorial line—illustrates a critical gap in stateless deployment workflows: no guardrails between local file state and remote storage truth. This post documents the technical root cause, the infrastructure checks we added, and the decision framework that prevents it from recurring.

The Failure Mode: Local State Trusted Over Remote Truth

Our deployment pipeline for queenofsandiego.com uses a simple push model:

aws s3 cp /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html s3://queenofsandiego-prod/ --region us-west-2

This command is stateless—it has no knowledge of what's already in S3, no diff capability, and no record of which version we're pushing or when. The session that caused the regression made edits to a local copy that hadn't been pulled from S3 in hours. Those edits were merged, tested locally, and deployed. But they didn't account for concurrent changes made directly to the S3 object during that window (in this case, Stripe checkout updates and CSS refinements that other sessions had pushed separately).

The underlying assumption—"my local file is the source of truth"—fails in multi-session, multi-tool environments where S3 is the actual source of truth for production.

Root Cause: No Pre-Deployment Diff or Pull-First Pattern

Two technical failures converged:

  • No pull-before-edit: The session began with a stale local copy. Git tracked the file, but deployment is not git-backed; it's direct S3 sync. The mental model was "I edited this file in my repo, so I can push it"—valid for git, dangerous for S3.
  • No diff-and-verify: The deployment included no step to compare the local file against the live S3 object, identify conflicts, or pause for manual review.
  • Batch deployment without targeting: Both staging and prod were deployed in one command, skipping the safety valve of staging-first validation.

Additionally, a prior session summary had warned: "snapshot prod before overwriting; S3 versioning is off." That instruction was in the session notes and was ignored.

Technical Fix: Eight Hardened Rules in CLAUDE.md

We added a deployment ruleset to /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, auto-loaded into every session context for that repo. The rules are:

  • D1 — Pull S3 and diff before any edit: Before modifying index.html, fetch the current prod object and do a local diff:
    aws s3 cp s3://queenofsandiego-prod/index.html ./index.html.prod-current --region us-west-2
    diff -u index.html.prod-current index.html | head -50
    If the output shows changes you didn't make, escalate to CB before proceeding.
  • D2 — Staging first, single target: Never deploy to prod and staging in the same command. Always test staging alone:
    aws s3 cp index.html s3://queenofsandiego-staging/ --region us-west-2
    Wait for CB to verify on staging.queenofsandiego.com. Only then deploy prod.
  • D3 — One logical change per deploy: If you edit index.html for the Stripe checkout and the hero CSS in one session, do two separate deployments (to staging, get approval, deploy prod; then repeat for the second change). This creates an audit trail and allows rollback of one feature without losing the other.
  • D4 — Obey your own prior warnings: If a session summary says "snapshot prod before overwriting," that's not a suggestion—it's a blocker. If you don't follow it, re-read why and escalate.
  • D5 — Snapshot prod before overwriting: S3 versioning is disabled on queenofsandiego-prod (to reduce costs). Before pushing index.html, save the current version locally:
    aws s3 cp s3://queenofsandiego-prod/index.html ./snapshots/index.html.$(date +%s) --region us-west-2
    Check that the snapshot is readable; if it's empty or corrupted, halt and escalate.
  • D6 — Six-line proof block: In chat, before running any cp command to prod, print:
    FILE: index.html
    SOURCE: /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html
    TARGET: s3://queenofsandiego-prod/ (dist ID: [CloudFront dist])
    SNAPSHOT: snapshots/index.html.[timestamp]
    DIFF LINES CHANGED: [number]
    FEATURES TOUCHED: [Stripe checkout, hero CSS, etc.]
    If any line is unknown or uncertain, ask CB before proceeding.
  • D7 — Feature-token registry: Maintain a grep-able list of production features in index.html (e.g., , ). Before deploying, run:
    grep -o '' index.html | sort | uniq
    Compare against the prior snapshot. If a token is missing, ask CB whether it was intentionally removed or is a regression.
  • D8 — Escalate if S3 is ahead of local: If the diff shows prod has content your local copy doesn't, or timestamps on S3 are newer, do not deploy. Escalate immediately to CB with the full diff and the names of sessions that may have touched the file.

Infrastructure: CloudFront and S3 Configuration

The queenofsandiego.com distribution uses:

  • S3 bucket: queenofsandiego-prod (us-west-2). Versioning: disabled. Public read via bucket policy.
  • CloudFront distribution: Cached via distribution ID (e.g., E1A2B3C4D5E6F7). Default object: index.html. TTL: 300 seconds (5 minutes).
  • Invalidation path: After any cp to S3, we manually invalidate CloudFront to flush caches:
    aws cloudfront create-invalidation --distribution-id E1A2B3C4D5E6F7 --paths "/*" --region us-west-2

Because versioning is off, snapshots (rule D5) are our only rollback mechanism. This is a cost trade-