```html

Preventing Stale-File Deployments: Hardening S3 Push Workflows with Pre-Flight Validation

Last week, a regression on queenofsandiego.com demonstrated a critical failure mode in our S3 deployment pipeline: pushing a stale local index.html over a newer production version, which silently wiped three features (hero crossfade animation, Stripe embedded checkout, and removed copy). This post documents the root cause, the eight hard rules we implemented to prevent recurrence, and the architectural patterns that make this preventable rather than inevitable.

What Went Wrong: The Stale-File Incident

On a routine session, an agent deployed a local copy of /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html directly to the S3 bucket queenofsandiego.com (CloudFront distribution ID: E1234..., masked for security) without:

  • Pulling the current S3 version first to detect drift
  • Diffing local against remote before write
  • Deploying to staging.queenofsandiego.com first for review
  • Printing a proof block (file hash, feature checklist, timestamp) in chat before execution
  • Creating an S3 versioning snapshot (to rollback if needed)

The local file was 24 hours stale. The production S3 version contained the JADA hero-to-BOOK-NOW crossfade (implemented via CSS @keyframes jada-to-book), inline Stripe <stripe-pricing-table> embed, and removal of the "For Ranch & Coast readers..." hero copy. Overwriting it restored all three deletions.

Why did this happen? The agent had received a prior session-summary warning that local files were behind S3, but the warning lived in external memory and wasn't surfaced at decision time. The deploy command was executed before a diff could be reviewed.

Technical Details: The Eight Hard Rules

We codified prevention into eight non-negotiable rules, auto-loaded from /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md at the start of every session:

D1: Pull-and-Diff Before Any Edit

Before touching any file in /sites/queenofsandiego.com/, pull the current version from S3:

aws s3 cp s3://queenofsandiego.com/index.html ./index.html.remote
diff -u index.html.remote index.html | head -100

If remote is newer or differs, document the delta in memory before proceeding. Do not assume local is current.

D2: Staging-Only, Single-Target Deploys

Never deploy to production and staging in one command. Always deploy to staging first:

aws s3 cp index.html s3://staging.queenofsandiego.com/index.html
# Wait for CloudFront invalidation on staging dist (E5678...)
# CB reviews staging.queenofsandiego.com
# Only then:
aws s3 cp index.html s3://queenofsandiego.com/index.html

This creates a gate: the human reviews the live staging site before prod goes live.

D3: One Logical Change Per Deploy

Group file changes by feature, not by "all files I edited today." If you rewrote the hero section AND fixed the pricing table, that's two deploys. This makes rollbacks surgical and blame-assignment clear.

D4: Obey Prior Session Warnings

If a prior session's MEMORY or summary warned that "local is stale" or "S3 is ahead," escalate to CB before deploying. Add a pointer to CLAUDE.md. Do not assume the warning is obsolete.

D5: Snapshot Prod Before Overwrite

S3 versioning is disabled (cost/complexity tradeoff). Instead, before any cp to production:

aws s3 cp s3://queenofsandiego.com/index.html ./backups/index.html.prod.$(date +%s)

Store the backup locally. This is your safety net.

D6: Print a Proof Block Before Deploy

Before any aws s3 cp, print (in chat, in a code block) a six-line proof that includes:

PROOF BLOCK — index.html to prod
SHA256: [first 12 chars of local hash]
Size: [bytes]
Features verified:
  ✓ JADA→BOOK-NOW crossfade (@keyframes jada-to-book present)
  ✓ Stripe embedded checkout (<stripe-pricing-table> tag found)
  ✓ Ranch & Coast copy removed (grep returns 0 matches)
Timestamp: [ISO 8601]
CB approval required? [YES/NO based on D4]

Print this before the command executes. If you can't generate an honest proof block, stop and escalate.

D7: Feature Token Registry

Maintain a live list of critical feature markers in CLAUDE.md:

## Feature Tokens (grep to verify before deploy)
- Hero crossfade: @keyframes jada-to-book
- Stripe checkout: <stripe-pricing-table
- Ranch & Coast removal: "For Ranch & Coast" (should NOT appear)
- Referral code input: id="referral-code-input"

Before any deploy, grep your local file against these tokens and include the results in the proof block (D6).

D8: Escalate to CB if S3 is Ahead

If diff index.html.remote index.html shows remote has content local doesn't, stop. Message CB with the diff. Do not overwrite without explicit sign-off. This is the circuit breaker.

Infrastructure Decisions

Why no S3 versioning? Queenofsandiego.com is a static site (~50 MB total, ~3.6K-line index.html). S3 versioning costs scale with write volume and storage. For this use case, local snapshots (D5) are cheaper and give us enough recovery window (24–48 hours, manually restored). If we deployed 20+ times per day, versioning would make sense; we don't.

Why staging first, not a local preview? Local browser testing misses CloudFront edge behavior, cache headers, and CSS/JS load paths that S3 + CloudFront introduces. Staging at staging.queenofsandiego.com (separate S3 bucket, separate CloudFront dist E5678...) is identical infrastructure to prod, so testing there is real testing.

Why a feature token registry? Feature tokens are grep-able strings that prove a feature is present. They're immune to whitespace/formatting changes and intentional (not accidental). A crossfade animation lives in a specific CSS class or keyframe name; a Stripe checkout requires a specific HTML tag. By registering these, we can programmatically verify them without manual code review.

Workflow: Deploying Safely Going Forward

The next time a